diff --git a/documentation/docs-roq/content/2.0.0/guides/collecting-items.md b/documentation/docs-roq/content/2.0.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.0.0/guides/combining-items.md b/documentation/docs-roq/content/2.0.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.0.0/guides/completion-stage.md b/documentation/docs-roq/content/2.0.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.0.0/guides/context-passing.md b/documentation/docs-roq/content/2.0.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.0.0/guides/controlling-demand.md b/documentation/docs-roq/content/2.0.0/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.0.0/guides/converters.md b/documentation/docs-roq/content/2.0.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.0.0/guides/custom-operators.md b/documentation/docs-roq/content/2.0.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.0.0/guides/delaying-events.md b/documentation/docs-roq/content/2.0.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.0.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.0.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.0.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.0.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/guides/emission-threads.md b/documentation/docs-roq/content/2.0.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.0.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.0.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/guides/filtering-items.md b/documentation/docs-roq/content/2.0.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.0.0/guides/framework-integration.md b/documentation/docs-roq/content/2.0.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.0.0/guides/handling-null.md b/documentation/docs-roq/content/2.0.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/guides/handling-timeouts.md b/documentation/docs-roq/content/2.0.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/guides/hot-streams.md b/documentation/docs-roq/content/2.0.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.0.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.0.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.0.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.0.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/guides/joining-unis.md b/documentation/docs-roq/content/2.0.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.0.0/guides/kotlin.md b/documentation/docs-roq/content/2.0.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.0.0/guides/logging.md b/documentation/docs-roq/content/2.0.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.0.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.0.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..e0f2d6c57 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,128 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + A->>M: onItem(2) + A->>M: onItem(3) + + A-->>M: onCompletion() + + M-->>B: subscribe + B-->>M: onSubscribe(s) + + B->>M: onItem(a) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.0.0/guides/pagination.md b/documentation/docs-roq/content/2.0.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/guides/polling.md b/documentation/docs-roq/content/2.0.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.0.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/guides/replaying-multis.md b/documentation/docs-roq/content/2.0.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/guides/rx.md b/documentation/docs-roq/content/2.0.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.0.0/guides/shortcut-methods.md b/documentation/docs-roq/content/2.0.0/guides/shortcut-methods.md new file mode 100644 index 000000000..acb31d221 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/shortcut-methods.md @@ -0,0 +1,46 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.then(() -> uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.0.0/guides/spies.md b/documentation/docs-roq/content/2.0.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/guides/take-skip-items.md b/documentation/docs-roq/content/2.0.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.0.0/guides/testing.md b/documentation/docs-roq/content/2.0.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.0.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.0.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.0.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.0.0/reference/publications.md b/documentation/docs-roq/content/2.0.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/reference/uni-and-multi.md b/documentation/docs-roq/content/2.0.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.0.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.0.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.0.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.0.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.0.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.0.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.0.0/tags-index.md b/documentation/docs-roq/content/2.0.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.0.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.0.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.0.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.0.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.0.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.0.0/tutorials/handling-failures.md b/documentation/docs-roq/content/2.0.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.0.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.0.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..ab8237aed --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.0.0/tutorials/observing-events.md b/documentation/docs-roq/content/2.0.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.0.0/tutorials/retrying.md b/documentation/docs-roq/content/2.0.0/tutorials/retrying.md new file mode 100644 index 000000000..7260c30b6 --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/tutorials/retrying.md @@ -0,0 +1,63 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.0.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.0.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..6113c50eb --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,127 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.0.0/tutorials/transforming-items.md b/documentation/docs-roq/content/2.0.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.0.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.1.0/guides/collecting-items.md b/documentation/docs-roq/content/2.1.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.1.0/guides/combining-items.md b/documentation/docs-roq/content/2.1.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.1.0/guides/completion-stage.md b/documentation/docs-roq/content/2.1.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.1.0/guides/context-passing.md b/documentation/docs-roq/content/2.1.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.1.0/guides/controlling-demand.md b/documentation/docs-roq/content/2.1.0/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.1.0/guides/converters.md b/documentation/docs-roq/content/2.1.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.1.0/guides/custom-operators.md b/documentation/docs-roq/content/2.1.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.1.0/guides/delaying-events.md b/documentation/docs-roq/content/2.1.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.1.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.1.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.1.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.1.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/guides/emission-threads.md b/documentation/docs-roq/content/2.1.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.1.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.1.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/guides/filtering-items.md b/documentation/docs-roq/content/2.1.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.1.0/guides/framework-integration.md b/documentation/docs-roq/content/2.1.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.1.0/guides/handling-null.md b/documentation/docs-roq/content/2.1.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/guides/handling-timeouts.md b/documentation/docs-roq/content/2.1.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/guides/hot-streams.md b/documentation/docs-roq/content/2.1.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.1.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.1.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.1.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.1.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/guides/joining-unis.md b/documentation/docs-roq/content/2.1.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.1.0/guides/kotlin.md b/documentation/docs-roq/content/2.1.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.1.0/guides/logging.md b/documentation/docs-roq/content/2.1.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.1.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.1.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.1.0/guides/pagination.md b/documentation/docs-roq/content/2.1.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/guides/polling.md b/documentation/docs-roq/content/2.1.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.1.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/guides/replaying-multis.md b/documentation/docs-roq/content/2.1.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/guides/rx.md b/documentation/docs-roq/content/2.1.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.1.0/guides/shortcut-methods.md b/documentation/docs-roq/content/2.1.0/guides/shortcut-methods.md new file mode 100644 index 000000000..acb31d221 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/shortcut-methods.md @@ -0,0 +1,46 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.then(() -> uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.1.0/guides/spies.md b/documentation/docs-roq/content/2.1.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/guides/take-skip-items.md b/documentation/docs-roq/content/2.1.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.1.0/guides/testing.md b/documentation/docs-roq/content/2.1.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.1.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.1.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.1.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.1.0/reference/publications.md b/documentation/docs-roq/content/2.1.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/reference/uni-and-multi.md b/documentation/docs-roq/content/2.1.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.1.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.1.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.1.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.1.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.1.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.1.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.1.0/tags-index.md b/documentation/docs-roq/content/2.1.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.1.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.1.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.1.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.1.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.1.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.1.0/tutorials/handling-failures.md b/documentation/docs-roq/content/2.1.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.1.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.1.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..ab8237aed --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.1.0/tutorials/observing-events.md b/documentation/docs-roq/content/2.1.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.1.0/tutorials/retrying.md b/documentation/docs-roq/content/2.1.0/tutorials/retrying.md new file mode 100644 index 000000000..7260c30b6 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/tutorials/retrying.md @@ -0,0 +1,63 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.1.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.1.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..a5202bf30 --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,127 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.1.0/tutorials/transforming-items.md b/documentation/docs-roq/content/2.1.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.1.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.2.0/guides/collecting-items.md b/documentation/docs-roq/content/2.2.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.2.0/guides/combining-items.md b/documentation/docs-roq/content/2.2.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.2.0/guides/completion-stage.md b/documentation/docs-roq/content/2.2.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.2.0/guides/context-passing.md b/documentation/docs-roq/content/2.2.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.2.0/guides/controlling-demand.md b/documentation/docs-roq/content/2.2.0/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.2.0/guides/converters.md b/documentation/docs-roq/content/2.2.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.2.0/guides/custom-operators.md b/documentation/docs-roq/content/2.2.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.2.0/guides/delaying-events.md b/documentation/docs-roq/content/2.2.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.2.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.2.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.2.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.2.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/guides/emission-threads.md b/documentation/docs-roq/content/2.2.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.2.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.2.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/guides/filtering-items.md b/documentation/docs-roq/content/2.2.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.2.0/guides/framework-integration.md b/documentation/docs-roq/content/2.2.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.2.0/guides/handling-null.md b/documentation/docs-roq/content/2.2.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/guides/handling-timeouts.md b/documentation/docs-roq/content/2.2.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/guides/hot-streams.md b/documentation/docs-roq/content/2.2.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.2.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.2.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.2.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.2.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/guides/joining-unis.md b/documentation/docs-roq/content/2.2.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.2.0/guides/kotlin.md b/documentation/docs-roq/content/2.2.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.2.0/guides/logging.md b/documentation/docs-roq/content/2.2.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.2.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.2.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.2.0/guides/pagination.md b/documentation/docs-roq/content/2.2.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/guides/polling.md b/documentation/docs-roq/content/2.2.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.2.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/guides/replaying-multis.md b/documentation/docs-roq/content/2.2.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/guides/rx.md b/documentation/docs-roq/content/2.2.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.2.0/guides/shortcut-methods.md b/documentation/docs-roq/content/2.2.0/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.2.0/guides/spies.md b/documentation/docs-roq/content/2.2.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/guides/take-skip-items.md b/documentation/docs-roq/content/2.2.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.2.0/guides/testing.md b/documentation/docs-roq/content/2.2.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.2.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.2.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.2.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.2.0/reference/publications.md b/documentation/docs-roq/content/2.2.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/reference/uni-and-multi.md b/documentation/docs-roq/content/2.2.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.2.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.2.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.2.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.2.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.2.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.2.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.2.0/tags-index.md b/documentation/docs-roq/content/2.2.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.2.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.2.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.2.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.2.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.2.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.2.0/tutorials/handling-failures.md b/documentation/docs-roq/content/2.2.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.2.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.2.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..ab8237aed --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.2.0/tutorials/observing-events.md b/documentation/docs-roq/content/2.2.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.2.0/tutorials/retrying.md b/documentation/docs-roq/content/2.2.0/tutorials/retrying.md new file mode 100644 index 000000000..7260c30b6 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/tutorials/retrying.md @@ -0,0 +1,63 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.2.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.2.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..a5202bf30 --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,127 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.2.0/tutorials/transforming-items.md b/documentation/docs-roq/content/2.2.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.2.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.3.0/guides/collecting-items.md b/documentation/docs-roq/content/2.3.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.3.0/guides/combining-items.md b/documentation/docs-roq/content/2.3.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.3.0/guides/completion-stage.md b/documentation/docs-roq/content/2.3.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.3.0/guides/context-passing.md b/documentation/docs-roq/content/2.3.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.3.0/guides/controlling-demand.md b/documentation/docs-roq/content/2.3.0/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.3.0/guides/converters.md b/documentation/docs-roq/content/2.3.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.3.0/guides/custom-operators.md b/documentation/docs-roq/content/2.3.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.3.0/guides/delaying-events.md b/documentation/docs-roq/content/2.3.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.3.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.3.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.3.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.3.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/guides/emission-threads.md b/documentation/docs-roq/content/2.3.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.3.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.3.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/guides/filtering-items.md b/documentation/docs-roq/content/2.3.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.3.0/guides/framework-integration.md b/documentation/docs-roq/content/2.3.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.3.0/guides/handling-null.md b/documentation/docs-roq/content/2.3.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/guides/handling-timeouts.md b/documentation/docs-roq/content/2.3.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/guides/hot-streams.md b/documentation/docs-roq/content/2.3.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.3.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.3.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.3.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.3.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/guides/joining-unis.md b/documentation/docs-roq/content/2.3.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.3.0/guides/kotlin.md b/documentation/docs-roq/content/2.3.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.3.0/guides/logging.md b/documentation/docs-roq/content/2.3.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.3.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.3.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.3.0/guides/pagination.md b/documentation/docs-roq/content/2.3.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/guides/polling.md b/documentation/docs-roq/content/2.3.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.3.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/guides/replaying-multis.md b/documentation/docs-roq/content/2.3.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/guides/rx.md b/documentation/docs-roq/content/2.3.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.3.0/guides/shortcut-methods.md b/documentation/docs-roq/content/2.3.0/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.3.0/guides/spies.md b/documentation/docs-roq/content/2.3.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/guides/take-skip-items.md b/documentation/docs-roq/content/2.3.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.3.0/guides/testing.md b/documentation/docs-roq/content/2.3.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.3.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.3.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.3.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.3.0/reference/publications.md b/documentation/docs-roq/content/2.3.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/reference/uni-and-multi.md b/documentation/docs-roq/content/2.3.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.3.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.3.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.3.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.3.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.3.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.3.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.3.0/tags-index.md b/documentation/docs-roq/content/2.3.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.3.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.3.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.3.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.3.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.3.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.3.0/tutorials/handling-failures.md b/documentation/docs-roq/content/2.3.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.3.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.3.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..ab8237aed --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.0/tutorials/observing-events.md b/documentation/docs-roq/content/2.3.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.3.0/tutorials/retrying.md b/documentation/docs-roq/content/2.3.0/tutorials/retrying.md new file mode 100644 index 000000000..7260c30b6 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/tutorials/retrying.md @@ -0,0 +1,63 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.3.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.3.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..a5202bf30 --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,127 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.3.0/tutorials/transforming-items.md b/documentation/docs-roq/content/2.3.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.3.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.3.1/guides/collecting-items.md b/documentation/docs-roq/content/2.3.1/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.3.1/guides/combining-items.md b/documentation/docs-roq/content/2.3.1/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.3.1/guides/completion-stage.md b/documentation/docs-roq/content/2.3.1/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.3.1/guides/context-passing.md b/documentation/docs-roq/content/2.3.1/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.3.1/guides/controlling-demand.md b/documentation/docs-roq/content/2.3.1/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.3.1/guides/converters.md b/documentation/docs-roq/content/2.3.1/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.3.1/guides/custom-operators.md b/documentation/docs-roq/content/2.3.1/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.3.1/guides/delaying-events.md b/documentation/docs-roq/content/2.3.1/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.3.1/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.3.1/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.3.1/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.3.1/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/guides/emission-threads.md b/documentation/docs-roq/content/2.3.1/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.3.1/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.3.1/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/guides/filtering-items.md b/documentation/docs-roq/content/2.3.1/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.3.1/guides/framework-integration.md b/documentation/docs-roq/content/2.3.1/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.3.1/guides/handling-null.md b/documentation/docs-roq/content/2.3.1/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/guides/handling-timeouts.md b/documentation/docs-roq/content/2.3.1/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/guides/hot-streams.md b/documentation/docs-roq/content/2.3.1/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.3.1/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.3.1/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.3.1/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.3.1/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/guides/joining-unis.md b/documentation/docs-roq/content/2.3.1/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.3.1/guides/kotlin.md b/documentation/docs-roq/content/2.3.1/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.3.1/guides/logging.md b/documentation/docs-roq/content/2.3.1/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.3.1/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.3.1/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.3.1/guides/pagination.md b/documentation/docs-roq/content/2.3.1/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/guides/polling.md b/documentation/docs-roq/content/2.3.1/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.3.1/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/guides/replaying-multis.md b/documentation/docs-roq/content/2.3.1/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/guides/rx.md b/documentation/docs-roq/content/2.3.1/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.3.1/guides/shortcut-methods.md b/documentation/docs-roq/content/2.3.1/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.3.1/guides/spies.md b/documentation/docs-roq/content/2.3.1/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/guides/take-skip-items.md b/documentation/docs-roq/content/2.3.1/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.3.1/guides/testing.md b/documentation/docs-roq/content/2.3.1/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.3.1/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.3.1/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.3.1/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.3.1/reference/publications.md b/documentation/docs-roq/content/2.3.1/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/reference/uni-and-multi.md b/documentation/docs-roq/content/2.3.1/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.3.1/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.3.1/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.3.1/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.3.1/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.3.1/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.3.1/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.3.1/tags-index.md b/documentation/docs-roq/content/2.3.1/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.3.1/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.3.1/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.3.1/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.3.1/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.3.1/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.3.1/tutorials/handling-failures.md b/documentation/docs-roq/content/2.3.1/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.3.1/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.3.1/tutorials/hello-mutiny.md new file mode 100644 index 000000000..ab8237aed --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.3.1/tutorials/observing-events.md b/documentation/docs-roq/content/2.3.1/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.3.1/tutorials/retrying.md b/documentation/docs-roq/content/2.3.1/tutorials/retrying.md new file mode 100644 index 000000000..7260c30b6 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/tutorials/retrying.md @@ -0,0 +1,63 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.3.1/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.3.1/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..a5202bf30 --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,127 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.3.1/tutorials/transforming-items.md b/documentation/docs-roq/content/2.3.1/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.3.1/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.4.0/guides/collecting-items.md b/documentation/docs-roq/content/2.4.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.4.0/guides/combining-items.md b/documentation/docs-roq/content/2.4.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.4.0/guides/completion-stage.md b/documentation/docs-roq/content/2.4.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.4.0/guides/context-passing.md b/documentation/docs-roq/content/2.4.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.4.0/guides/controlling-demand.md b/documentation/docs-roq/content/2.4.0/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.4.0/guides/converters.md b/documentation/docs-roq/content/2.4.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.4.0/guides/custom-operators.md b/documentation/docs-roq/content/2.4.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.4.0/guides/delaying-events.md b/documentation/docs-roq/content/2.4.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.4.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.4.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.4.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.4.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/guides/emission-threads.md b/documentation/docs-roq/content/2.4.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.4.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.4.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/guides/filtering-items.md b/documentation/docs-roq/content/2.4.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.4.0/guides/framework-integration.md b/documentation/docs-roq/content/2.4.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.4.0/guides/handling-null.md b/documentation/docs-roq/content/2.4.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/guides/handling-timeouts.md b/documentation/docs-roq/content/2.4.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/guides/hot-streams.md b/documentation/docs-roq/content/2.4.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.4.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.4.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.4.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.4.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/guides/joining-unis.md b/documentation/docs-roq/content/2.4.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.4.0/guides/kotlin.md b/documentation/docs-roq/content/2.4.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.4.0/guides/logging.md b/documentation/docs-roq/content/2.4.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.4.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.4.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.4.0/guides/multi-split.md b/documentation/docs-roq/content/2.4.0/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.4.0/guides/pagination.md b/documentation/docs-roq/content/2.4.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/guides/polling.md b/documentation/docs-roq/content/2.4.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.4.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/guides/replaying-multis.md b/documentation/docs-roq/content/2.4.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/guides/rx.md b/documentation/docs-roq/content/2.4.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.4.0/guides/shortcut-methods.md b/documentation/docs-roq/content/2.4.0/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.4.0/guides/spies.md b/documentation/docs-roq/content/2.4.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/guides/take-skip-items.md b/documentation/docs-roq/content/2.4.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.4.0/guides/testing.md b/documentation/docs-roq/content/2.4.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.4.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.4.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.4.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.4.0/reference/publications.md b/documentation/docs-roq/content/2.4.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/reference/uni-and-multi.md b/documentation/docs-roq/content/2.4.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.4.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.4.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.4.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.4.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.4.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.4.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.4.0/tags-index.md b/documentation/docs-roq/content/2.4.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.4.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.4.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.4.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.4.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.4.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.4.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.4.0/tutorials/handling-failures.md b/documentation/docs-roq/content/2.4.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.4.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.4.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.4.0/tutorials/observing-events.md b/documentation/docs-roq/content/2.4.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.4.0/tutorials/retrying.md b/documentation/docs-roq/content/2.4.0/tutorials/retrying.md new file mode 100644 index 000000000..7260c30b6 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/tutorials/retrying.md @@ -0,0 +1,63 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.4.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.4.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.4.0/tutorials/transforming-items.md b/documentation/docs-roq/content/2.4.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.4.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.5.0/guides/branching.md b/documentation/docs-roq/content/2.5.0/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.5.0/guides/collecting-items.md b/documentation/docs-roq/content/2.5.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.5.0/guides/combining-items.md b/documentation/docs-roq/content/2.5.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.5.0/guides/completion-stage.md b/documentation/docs-roq/content/2.5.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.5.0/guides/context-passing.md b/documentation/docs-roq/content/2.5.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.5.0/guides/controlling-demand.md b/documentation/docs-roq/content/2.5.0/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.5.0/guides/converters.md b/documentation/docs-roq/content/2.5.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.5.0/guides/custom-operators.md b/documentation/docs-roq/content/2.5.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.5.0/guides/delaying-events.md b/documentation/docs-roq/content/2.5.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.5.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.5.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.5.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.5.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/guides/emission-threads.md b/documentation/docs-roq/content/2.5.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.5.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.5.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/guides/filtering-items.md b/documentation/docs-roq/content/2.5.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.5.0/guides/framework-integration.md b/documentation/docs-roq/content/2.5.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.5.0/guides/handling-null.md b/documentation/docs-roq/content/2.5.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/guides/handling-timeouts.md b/documentation/docs-roq/content/2.5.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/guides/hot-streams.md b/documentation/docs-roq/content/2.5.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.5.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.5.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.5.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.5.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/guides/joining-unis.md b/documentation/docs-roq/content/2.5.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.5.0/guides/kotlin.md b/documentation/docs-roq/content/2.5.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.5.0/guides/logging.md b/documentation/docs-roq/content/2.5.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.5.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.5.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.5.0/guides/multi-split.md b/documentation/docs-roq/content/2.5.0/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.5.0/guides/pagination.md b/documentation/docs-roq/content/2.5.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/guides/polling.md b/documentation/docs-roq/content/2.5.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.5.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/guides/replaying-multis.md b/documentation/docs-roq/content/2.5.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/guides/rx.md b/documentation/docs-roq/content/2.5.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.5.0/guides/shortcut-methods.md b/documentation/docs-roq/content/2.5.0/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.5.0/guides/spies.md b/documentation/docs-roq/content/2.5.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/guides/take-skip-items.md b/documentation/docs-roq/content/2.5.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.5.0/guides/testing.md b/documentation/docs-roq/content/2.5.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.5.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.5.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.5.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.5.0/reference/publications.md b/documentation/docs-roq/content/2.5.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/reference/uni-and-multi.md b/documentation/docs-roq/content/2.5.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.5.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.5.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.5.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.5.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.5.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.5.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.5.0/tags-index.md b/documentation/docs-roq/content/2.5.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.5.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.5.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.5.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.5.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.5.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.5.0/tutorials/handling-failures.md b/documentation/docs-roq/content/2.5.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.5.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.5.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.5.0/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.5.0/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.5.0/tutorials/observing-events.md b/documentation/docs-roq/content/2.5.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.5.0/tutorials/retrying.md b/documentation/docs-roq/content/2.5.0/tutorials/retrying.md new file mode 100644 index 000000000..7260c30b6 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/tutorials/retrying.md @@ -0,0 +1,63 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.5.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.5.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.5.0/tutorials/transforming-items.md b/documentation/docs-roq/content/2.5.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.5.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.5.1/guides/branching.md b/documentation/docs-roq/content/2.5.1/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.5.1/guides/collecting-items.md b/documentation/docs-roq/content/2.5.1/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.5.1/guides/combining-items.md b/documentation/docs-roq/content/2.5.1/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.5.1/guides/completion-stage.md b/documentation/docs-roq/content/2.5.1/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.5.1/guides/context-passing.md b/documentation/docs-roq/content/2.5.1/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.5.1/guides/controlling-demand.md b/documentation/docs-roq/content/2.5.1/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.5.1/guides/converters.md b/documentation/docs-roq/content/2.5.1/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.5.1/guides/custom-operators.md b/documentation/docs-roq/content/2.5.1/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.5.1/guides/delaying-events.md b/documentation/docs-roq/content/2.5.1/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.5.1/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.5.1/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.5.1/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.5.1/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/guides/emission-threads.md b/documentation/docs-roq/content/2.5.1/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.5.1/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.5.1/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/guides/filtering-items.md b/documentation/docs-roq/content/2.5.1/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.5.1/guides/framework-integration.md b/documentation/docs-roq/content/2.5.1/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.5.1/guides/handling-null.md b/documentation/docs-roq/content/2.5.1/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/guides/handling-timeouts.md b/documentation/docs-roq/content/2.5.1/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/guides/hot-streams.md b/documentation/docs-roq/content/2.5.1/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.5.1/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.5.1/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.5.1/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.5.1/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/guides/joining-unis.md b/documentation/docs-roq/content/2.5.1/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.5.1/guides/kotlin.md b/documentation/docs-roq/content/2.5.1/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.5.1/guides/logging.md b/documentation/docs-roq/content/2.5.1/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.5.1/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.5.1/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.5.1/guides/multi-split.md b/documentation/docs-roq/content/2.5.1/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.5.1/guides/pagination.md b/documentation/docs-roq/content/2.5.1/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/guides/polling.md b/documentation/docs-roq/content/2.5.1/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.5.1/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/guides/replaying-multis.md b/documentation/docs-roq/content/2.5.1/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/guides/rx.md b/documentation/docs-roq/content/2.5.1/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.5.1/guides/shortcut-methods.md b/documentation/docs-roq/content/2.5.1/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.5.1/guides/spies.md b/documentation/docs-roq/content/2.5.1/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/guides/take-skip-items.md b/documentation/docs-roq/content/2.5.1/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.5.1/guides/testing.md b/documentation/docs-roq/content/2.5.1/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.5.1/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.5.1/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.5.1/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.5.1/reference/publications.md b/documentation/docs-roq/content/2.5.1/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/reference/uni-and-multi.md b/documentation/docs-roq/content/2.5.1/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.5.1/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.5.1/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.5.1/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.5.1/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.5.1/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.5.1/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.5.1/tags-index.md b/documentation/docs-roq/content/2.5.1/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.1/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.5.1/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.5.1/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.5.1/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.5.1/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.5.1/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.5.1/tutorials/handling-failures.md b/documentation/docs-roq/content/2.5.1/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.5.1/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.5.1/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.5.1/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.5.1/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.5.1/tutorials/observing-events.md b/documentation/docs-roq/content/2.5.1/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.5.1/tutorials/retrying.md b/documentation/docs-roq/content/2.5.1/tutorials/retrying.md new file mode 100644 index 000000000..7260c30b6 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/tutorials/retrying.md @@ -0,0 +1,63 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.5.1/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.5.1/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.5.1/tutorials/transforming-items.md b/documentation/docs-roq/content/2.5.1/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.5.1/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.5.2/guides/branching.md b/documentation/docs-roq/content/2.5.2/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.5.2/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.5.2/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.5.2/guides/collecting-items.md b/documentation/docs-roq/content/2.5.2/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.5.2/guides/combining-items.md b/documentation/docs-roq/content/2.5.2/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.5.2/guides/completion-stage.md b/documentation/docs-roq/content/2.5.2/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.5.2/guides/context-passing.md b/documentation/docs-roq/content/2.5.2/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.5.2/guides/controlling-demand.md b/documentation/docs-roq/content/2.5.2/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.5.2/guides/converters.md b/documentation/docs-roq/content/2.5.2/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.5.2/guides/custom-operators.md b/documentation/docs-roq/content/2.5.2/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.5.2/guides/delaying-events.md b/documentation/docs-roq/content/2.5.2/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.5.2/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.5.2/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.5.2/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.5.2/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/guides/emission-threads.md b/documentation/docs-roq/content/2.5.2/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.5.2/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.5.2/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/guides/filtering-items.md b/documentation/docs-roq/content/2.5.2/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.5.2/guides/framework-integration.md b/documentation/docs-roq/content/2.5.2/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.5.2/guides/handling-null.md b/documentation/docs-roq/content/2.5.2/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/guides/handling-timeouts.md b/documentation/docs-roq/content/2.5.2/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/guides/hot-streams.md b/documentation/docs-roq/content/2.5.2/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.5.2/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.5.2/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.5.2/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.5.2/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/guides/joining-unis.md b/documentation/docs-roq/content/2.5.2/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.5.2/guides/kotlin.md b/documentation/docs-roq/content/2.5.2/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.5.2/guides/logging.md b/documentation/docs-roq/content/2.5.2/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.5.2/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.5.2/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.5.2/guides/multi-split.md b/documentation/docs-roq/content/2.5.2/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.5.2/guides/pagination.md b/documentation/docs-roq/content/2.5.2/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/guides/polling.md b/documentation/docs-roq/content/2.5.2/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.5.2/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/guides/replaying-multis.md b/documentation/docs-roq/content/2.5.2/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/guides/rx.md b/documentation/docs-roq/content/2.5.2/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.5.2/guides/shortcut-methods.md b/documentation/docs-roq/content/2.5.2/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.5.2/guides/spies.md b/documentation/docs-roq/content/2.5.2/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/guides/take-skip-items.md b/documentation/docs-roq/content/2.5.2/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.5.2/guides/testing.md b/documentation/docs-roq/content/2.5.2/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.5.2/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.5.2/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.5.2/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.5.2/reference/publications.md b/documentation/docs-roq/content/2.5.2/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/reference/uni-and-multi.md b/documentation/docs-roq/content/2.5.2/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.5.2/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.5.2/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.5.2/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.5.2/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.5.2/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.5.2/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.5.2/tags-index.md b/documentation/docs-roq/content/2.5.2/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.2/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.5.2/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.5.2/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.5.2/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.5.2/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.5.2/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.5.2/tutorials/handling-failures.md b/documentation/docs-roq/content/2.5.2/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.5.2/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.5.2/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.5.2/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.5.2/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.5.2/tutorials/observing-events.md b/documentation/docs-roq/content/2.5.2/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.5.2/tutorials/retrying.md b/documentation/docs-roq/content/2.5.2/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.5.2/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.5.2/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.5.2/tutorials/transforming-items.md b/documentation/docs-roq/content/2.5.2/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.5.2/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.5.3/guides/branching.md b/documentation/docs-roq/content/2.5.3/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.5.3/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.5.3/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.5.3/guides/collecting-items.md b/documentation/docs-roq/content/2.5.3/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.5.3/guides/combining-items.md b/documentation/docs-roq/content/2.5.3/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.5.3/guides/completion-stage.md b/documentation/docs-roq/content/2.5.3/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.5.3/guides/context-passing.md b/documentation/docs-roq/content/2.5.3/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.5.3/guides/controlling-demand.md b/documentation/docs-roq/content/2.5.3/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.5.3/guides/converters.md b/documentation/docs-roq/content/2.5.3/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.5.3/guides/custom-operators.md b/documentation/docs-roq/content/2.5.3/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.5.3/guides/delaying-events.md b/documentation/docs-roq/content/2.5.3/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.5.3/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.5.3/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.5.3/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.5.3/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/guides/emission-threads.md b/documentation/docs-roq/content/2.5.3/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.5.3/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.5.3/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/guides/filtering-items.md b/documentation/docs-roq/content/2.5.3/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.5.3/guides/framework-integration.md b/documentation/docs-roq/content/2.5.3/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.5.3/guides/handling-null.md b/documentation/docs-roq/content/2.5.3/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/guides/handling-timeouts.md b/documentation/docs-roq/content/2.5.3/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/guides/hot-streams.md b/documentation/docs-roq/content/2.5.3/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.5.3/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.5.3/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.5.3/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.5.3/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/guides/joining-unis.md b/documentation/docs-roq/content/2.5.3/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.5.3/guides/kotlin.md b/documentation/docs-roq/content/2.5.3/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.5.3/guides/logging.md b/documentation/docs-roq/content/2.5.3/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.5.3/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.5.3/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.5.3/guides/multi-split.md b/documentation/docs-roq/content/2.5.3/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.5.3/guides/pagination.md b/documentation/docs-roq/content/2.5.3/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/guides/polling.md b/documentation/docs-roq/content/2.5.3/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.5.3/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/guides/replaying-multis.md b/documentation/docs-roq/content/2.5.3/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/guides/rx.md b/documentation/docs-roq/content/2.5.3/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.5.3/guides/shortcut-methods.md b/documentation/docs-roq/content/2.5.3/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.5.3/guides/spies.md b/documentation/docs-roq/content/2.5.3/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/guides/take-skip-items.md b/documentation/docs-roq/content/2.5.3/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.5.3/guides/testing.md b/documentation/docs-roq/content/2.5.3/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.5.3/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.5.3/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.5.3/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.5.3/reference/publications.md b/documentation/docs-roq/content/2.5.3/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/reference/uni-and-multi.md b/documentation/docs-roq/content/2.5.3/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.5.3/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.5.3/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.5.3/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.5.3/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.5.3/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.5.3/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.5.3/tags-index.md b/documentation/docs-roq/content/2.5.3/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.3/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.5.3/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.5.3/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.5.3/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.5.3/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.5.3/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.5.3/tutorials/handling-failures.md b/documentation/docs-roq/content/2.5.3/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.5.3/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.5.3/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.5.3/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.5.3/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.5.3/tutorials/observing-events.md b/documentation/docs-roq/content/2.5.3/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.5.3/tutorials/retrying.md b/documentation/docs-roq/content/2.5.3/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.5.3/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.5.3/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.5.3/tutorials/transforming-items.md b/documentation/docs-roq/content/2.5.3/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.5.3/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.5.4/guides/branching.md b/documentation/docs-roq/content/2.5.4/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.5.4/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.5.4/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.5.4/guides/collecting-items.md b/documentation/docs-roq/content/2.5.4/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.5.4/guides/combining-items.md b/documentation/docs-roq/content/2.5.4/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.5.4/guides/completion-stage.md b/documentation/docs-roq/content/2.5.4/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.5.4/guides/context-passing.md b/documentation/docs-roq/content/2.5.4/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.5.4/guides/controlling-demand.md b/documentation/docs-roq/content/2.5.4/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.5.4/guides/converters.md b/documentation/docs-roq/content/2.5.4/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.5.4/guides/custom-operators.md b/documentation/docs-roq/content/2.5.4/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.5.4/guides/delaying-events.md b/documentation/docs-roq/content/2.5.4/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.5.4/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.5.4/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.5.4/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.5.4/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/guides/emission-threads.md b/documentation/docs-roq/content/2.5.4/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.5.4/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.5.4/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/guides/filtering-items.md b/documentation/docs-roq/content/2.5.4/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.5.4/guides/framework-integration.md b/documentation/docs-roq/content/2.5.4/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.5.4/guides/handling-null.md b/documentation/docs-roq/content/2.5.4/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/guides/handling-timeouts.md b/documentation/docs-roq/content/2.5.4/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/guides/hot-streams.md b/documentation/docs-roq/content/2.5.4/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.5.4/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.5.4/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.5.4/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.5.4/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/guides/joining-unis.md b/documentation/docs-roq/content/2.5.4/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.5.4/guides/kotlin.md b/documentation/docs-roq/content/2.5.4/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.5.4/guides/logging.md b/documentation/docs-roq/content/2.5.4/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.5.4/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.5.4/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.5.4/guides/multi-split.md b/documentation/docs-roq/content/2.5.4/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.5.4/guides/pagination.md b/documentation/docs-roq/content/2.5.4/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/guides/polling.md b/documentation/docs-roq/content/2.5.4/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.5.4/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/guides/replaying-multis.md b/documentation/docs-roq/content/2.5.4/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/guides/rx.md b/documentation/docs-roq/content/2.5.4/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.5.4/guides/shortcut-methods.md b/documentation/docs-roq/content/2.5.4/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.5.4/guides/spies.md b/documentation/docs-roq/content/2.5.4/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/guides/take-skip-items.md b/documentation/docs-roq/content/2.5.4/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.5.4/guides/testing.md b/documentation/docs-roq/content/2.5.4/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.5.4/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.5.4/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.5.4/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.5.4/reference/publications.md b/documentation/docs-roq/content/2.5.4/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/reference/uni-and-multi.md b/documentation/docs-roq/content/2.5.4/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.5.4/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.5.4/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.5.4/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.5.4/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.5.4/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.5.4/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.5.4/tags-index.md b/documentation/docs-roq/content/2.5.4/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.4/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.5.4/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.5.4/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.5.4/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.5.4/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.5.4/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.5.4/tutorials/handling-failures.md b/documentation/docs-roq/content/2.5.4/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.5.4/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.5.4/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.5.4/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.5.4/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.5.4/tutorials/observing-events.md b/documentation/docs-roq/content/2.5.4/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.5.4/tutorials/retrying.md b/documentation/docs-roq/content/2.5.4/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.5.4/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.5.4/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.5.4/tutorials/transforming-items.md b/documentation/docs-roq/content/2.5.4/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.5.4/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.5.5/guides/branching.md b/documentation/docs-roq/content/2.5.5/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.5.5/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.5.5/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.5.5/guides/collecting-items.md b/documentation/docs-roq/content/2.5.5/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.5.5/guides/combining-items.md b/documentation/docs-roq/content/2.5.5/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.5.5/guides/completion-stage.md b/documentation/docs-roq/content/2.5.5/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.5.5/guides/context-passing.md b/documentation/docs-roq/content/2.5.5/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.5.5/guides/controlling-demand.md b/documentation/docs-roq/content/2.5.5/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.5.5/guides/converters.md b/documentation/docs-roq/content/2.5.5/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.5.5/guides/custom-operators.md b/documentation/docs-roq/content/2.5.5/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.5.5/guides/delaying-events.md b/documentation/docs-roq/content/2.5.5/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.5.5/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.5.5/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.5.5/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.5.5/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/guides/emission-threads.md b/documentation/docs-roq/content/2.5.5/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.5.5/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.5.5/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/guides/filtering-items.md b/documentation/docs-roq/content/2.5.5/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.5.5/guides/framework-integration.md b/documentation/docs-roq/content/2.5.5/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.5.5/guides/handling-null.md b/documentation/docs-roq/content/2.5.5/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/guides/handling-timeouts.md b/documentation/docs-roq/content/2.5.5/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/guides/hot-streams.md b/documentation/docs-roq/content/2.5.5/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.5.5/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.5.5/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.5.5/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.5.5/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/guides/joining-unis.md b/documentation/docs-roq/content/2.5.5/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.5.5/guides/kotlin.md b/documentation/docs-roq/content/2.5.5/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.5.5/guides/logging.md b/documentation/docs-roq/content/2.5.5/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.5.5/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.5.5/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.5.5/guides/multi-split.md b/documentation/docs-roq/content/2.5.5/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.5.5/guides/pagination.md b/documentation/docs-roq/content/2.5.5/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/guides/polling.md b/documentation/docs-roq/content/2.5.5/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.5.5/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/guides/replaying-multis.md b/documentation/docs-roq/content/2.5.5/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/guides/rx.md b/documentation/docs-roq/content/2.5.5/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.5.5/guides/shortcut-methods.md b/documentation/docs-roq/content/2.5.5/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.5.5/guides/spies.md b/documentation/docs-roq/content/2.5.5/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/guides/take-skip-items.md b/documentation/docs-roq/content/2.5.5/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.5.5/guides/testing.md b/documentation/docs-roq/content/2.5.5/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.5.5/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.5.5/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.5.5/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.5.5/reference/publications.md b/documentation/docs-roq/content/2.5.5/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/reference/uni-and-multi.md b/documentation/docs-roq/content/2.5.5/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.5.5/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.5.5/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.5.5/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.5.5/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.5.5/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.5.5/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.5.5/tags-index.md b/documentation/docs-roq/content/2.5.5/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.5/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.5.5/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.5.5/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.5.5/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.5.5/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.5.5/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.5.5/tutorials/handling-failures.md b/documentation/docs-roq/content/2.5.5/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.5.5/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.5.5/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.5.5/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.5.5/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.5.5/tutorials/observing-events.md b/documentation/docs-roq/content/2.5.5/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.5.5/tutorials/retrying.md b/documentation/docs-roq/content/2.5.5/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.5.5/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.5.5/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.5.5/tutorials/transforming-items.md b/documentation/docs-roq/content/2.5.5/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.5.5/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.5.6/guides/branching.md b/documentation/docs-roq/content/2.5.6/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.5.6/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.5.6/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.5.6/guides/collecting-items.md b/documentation/docs-roq/content/2.5.6/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.5.6/guides/combining-items.md b/documentation/docs-roq/content/2.5.6/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.5.6/guides/completion-stage.md b/documentation/docs-roq/content/2.5.6/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.5.6/guides/context-passing.md b/documentation/docs-roq/content/2.5.6/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.5.6/guides/controlling-demand.md b/documentation/docs-roq/content/2.5.6/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.5.6/guides/converters.md b/documentation/docs-roq/content/2.5.6/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.5.6/guides/custom-operators.md b/documentation/docs-roq/content/2.5.6/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.5.6/guides/delaying-events.md b/documentation/docs-roq/content/2.5.6/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.5.6/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.5.6/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.5.6/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.5.6/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/guides/emission-threads.md b/documentation/docs-roq/content/2.5.6/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.5.6/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.5.6/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/guides/filtering-items.md b/documentation/docs-roq/content/2.5.6/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.5.6/guides/framework-integration.md b/documentation/docs-roq/content/2.5.6/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.5.6/guides/handling-null.md b/documentation/docs-roq/content/2.5.6/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/guides/handling-timeouts.md b/documentation/docs-roq/content/2.5.6/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/guides/hot-streams.md b/documentation/docs-roq/content/2.5.6/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.5.6/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.5.6/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.5.6/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.5.6/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/guides/joining-unis.md b/documentation/docs-roq/content/2.5.6/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.5.6/guides/kotlin.md b/documentation/docs-roq/content/2.5.6/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.5.6/guides/logging.md b/documentation/docs-roq/content/2.5.6/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.5.6/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.5.6/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.5.6/guides/multi-split.md b/documentation/docs-roq/content/2.5.6/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.5.6/guides/pagination.md b/documentation/docs-roq/content/2.5.6/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/guides/polling.md b/documentation/docs-roq/content/2.5.6/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.5.6/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/guides/replaying-multis.md b/documentation/docs-roq/content/2.5.6/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/guides/rx.md b/documentation/docs-roq/content/2.5.6/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.5.6/guides/shortcut-methods.md b/documentation/docs-roq/content/2.5.6/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.5.6/guides/spies.md b/documentation/docs-roq/content/2.5.6/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/guides/take-skip-items.md b/documentation/docs-roq/content/2.5.6/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.5.6/guides/testing.md b/documentation/docs-roq/content/2.5.6/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.5.6/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.5.6/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.5.6/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.5.6/reference/publications.md b/documentation/docs-roq/content/2.5.6/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/reference/uni-and-multi.md b/documentation/docs-roq/content/2.5.6/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.5.6/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.5.6/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.5.6/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.5.6/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.5.6/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.5.6/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.5.6/tags-index.md b/documentation/docs-roq/content/2.5.6/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.5.6/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.5.6/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.5.6/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.5.6/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.5.6/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.5.6/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.5.6/tutorials/handling-failures.md b/documentation/docs-roq/content/2.5.6/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.5.6/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.5.6/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.5.6/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.5.6/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.5.6/tutorials/observing-events.md b/documentation/docs-roq/content/2.5.6/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.5.6/tutorials/retrying.md b/documentation/docs-roq/content/2.5.6/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.5.6/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.5.6/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.5.6/tutorials/transforming-items.md b/documentation/docs-roq/content/2.5.6/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.5.6/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.6.0/guides/branching.md b/documentation/docs-roq/content/2.6.0/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.6.0/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.6.0/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.6.0/guides/collecting-items.md b/documentation/docs-roq/content/2.6.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.6.0/guides/combining-items.md b/documentation/docs-roq/content/2.6.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.6.0/guides/completion-stage.md b/documentation/docs-roq/content/2.6.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.6.0/guides/context-passing.md b/documentation/docs-roq/content/2.6.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.6.0/guides/controlling-demand.md b/documentation/docs-roq/content/2.6.0/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.6.0/guides/converters.md b/documentation/docs-roq/content/2.6.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.6.0/guides/custom-operators.md b/documentation/docs-roq/content/2.6.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.6.0/guides/delaying-events.md b/documentation/docs-roq/content/2.6.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.6.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.6.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.6.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.6.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/guides/emission-threads.md b/documentation/docs-roq/content/2.6.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.6.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.6.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/guides/filtering-items.md b/documentation/docs-roq/content/2.6.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.6.0/guides/framework-integration.md b/documentation/docs-roq/content/2.6.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.6.0/guides/handling-null.md b/documentation/docs-roq/content/2.6.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/guides/handling-timeouts.md b/documentation/docs-roq/content/2.6.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/guides/hot-streams.md b/documentation/docs-roq/content/2.6.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.6.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.6.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.6.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.6.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/guides/joining-unis.md b/documentation/docs-roq/content/2.6.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.6.0/guides/kotlin.md b/documentation/docs-roq/content/2.6.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.6.0/guides/logging.md b/documentation/docs-roq/content/2.6.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.6.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.6.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.6.0/guides/multi-split.md b/documentation/docs-roq/content/2.6.0/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.6.0/guides/pagination.md b/documentation/docs-roq/content/2.6.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/guides/polling.md b/documentation/docs-roq/content/2.6.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.6.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/guides/replaying-multis.md b/documentation/docs-roq/content/2.6.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/guides/rx.md b/documentation/docs-roq/content/2.6.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.6.0/guides/shortcut-methods.md b/documentation/docs-roq/content/2.6.0/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.6.0/guides/spies.md b/documentation/docs-roq/content/2.6.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/guides/take-skip-items.md b/documentation/docs-roq/content/2.6.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.6.0/guides/testing.md b/documentation/docs-roq/content/2.6.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.6.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.6.0/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/2.6.0/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/2.6.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.6.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.6.0/reference/publications.md b/documentation/docs-roq/content/2.6.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/reference/uni-and-multi.md b/documentation/docs-roq/content/2.6.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.6.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.6.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.6.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.6.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.6.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.6.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.6.0/tags-index.md b/documentation/docs-roq/content/2.6.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.6.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.6.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.6.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.6.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.6.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.6.0/tutorials/handling-failures.md b/documentation/docs-roq/content/2.6.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.6.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.6.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.6.0/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.6.0/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.6.0/tutorials/observing-events.md b/documentation/docs-roq/content/2.6.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.6.0/tutorials/retrying.md b/documentation/docs-roq/content/2.6.0/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.6.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.6.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.6.0/tutorials/transforming-items.md b/documentation/docs-roq/content/2.6.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.6.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.6.1/guides/branching.md b/documentation/docs-roq/content/2.6.1/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.6.1/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.6.1/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.6.1/guides/collecting-items.md b/documentation/docs-roq/content/2.6.1/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.6.1/guides/combining-items.md b/documentation/docs-roq/content/2.6.1/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.6.1/guides/completion-stage.md b/documentation/docs-roq/content/2.6.1/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.6.1/guides/context-passing.md b/documentation/docs-roq/content/2.6.1/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.6.1/guides/controlling-demand.md b/documentation/docs-roq/content/2.6.1/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.6.1/guides/converters.md b/documentation/docs-roq/content/2.6.1/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.6.1/guides/custom-operators.md b/documentation/docs-roq/content/2.6.1/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.6.1/guides/delaying-events.md b/documentation/docs-roq/content/2.6.1/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.6.1/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.6.1/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.6.1/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.6.1/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/guides/emission-threads.md b/documentation/docs-roq/content/2.6.1/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.6.1/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.6.1/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/guides/filtering-items.md b/documentation/docs-roq/content/2.6.1/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.6.1/guides/framework-integration.md b/documentation/docs-roq/content/2.6.1/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.6.1/guides/handling-null.md b/documentation/docs-roq/content/2.6.1/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/guides/handling-timeouts.md b/documentation/docs-roq/content/2.6.1/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/guides/hot-streams.md b/documentation/docs-roq/content/2.6.1/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.6.1/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.6.1/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.6.1/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.6.1/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/guides/joining-unis.md b/documentation/docs-roq/content/2.6.1/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.6.1/guides/kotlin.md b/documentation/docs-roq/content/2.6.1/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.6.1/guides/logging.md b/documentation/docs-roq/content/2.6.1/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.6.1/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.6.1/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.6.1/guides/multi-split.md b/documentation/docs-roq/content/2.6.1/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.6.1/guides/pagination.md b/documentation/docs-roq/content/2.6.1/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/guides/polling.md b/documentation/docs-roq/content/2.6.1/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.6.1/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/guides/replaying-multis.md b/documentation/docs-roq/content/2.6.1/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/guides/rx.md b/documentation/docs-roq/content/2.6.1/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.6.1/guides/shortcut-methods.md b/documentation/docs-roq/content/2.6.1/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.6.1/guides/spies.md b/documentation/docs-roq/content/2.6.1/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/guides/take-skip-items.md b/documentation/docs-roq/content/2.6.1/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.6.1/guides/testing.md b/documentation/docs-roq/content/2.6.1/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.6.1/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.6.1/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/2.6.1/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/2.6.1/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.6.1/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.6.1/reference/publications.md b/documentation/docs-roq/content/2.6.1/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/reference/uni-and-multi.md b/documentation/docs-roq/content/2.6.1/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.6.1/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.6.1/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.6.1/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.6.1/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.6.1/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.6.1/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.6.1/tags-index.md b/documentation/docs-roq/content/2.6.1/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.1/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.6.1/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.6.1/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.6.1/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.6.1/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.6.1/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.6.1/tutorials/handling-failures.md b/documentation/docs-roq/content/2.6.1/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.6.1/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.6.1/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.6.1/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.6.1/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.6.1/tutorials/observing-events.md b/documentation/docs-roq/content/2.6.1/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.6.1/tutorials/retrying.md b/documentation/docs-roq/content/2.6.1/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.6.1/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.6.1/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.6.1/tutorials/transforming-items.md b/documentation/docs-roq/content/2.6.1/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.6.1/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.6.2/guides/branching.md b/documentation/docs-roq/content/2.6.2/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.6.2/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.6.2/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.6.2/guides/collecting-items.md b/documentation/docs-roq/content/2.6.2/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.6.2/guides/combining-items.md b/documentation/docs-roq/content/2.6.2/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.6.2/guides/completion-stage.md b/documentation/docs-roq/content/2.6.2/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.6.2/guides/context-passing.md b/documentation/docs-roq/content/2.6.2/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.6.2/guides/controlling-demand.md b/documentation/docs-roq/content/2.6.2/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.6.2/guides/converters.md b/documentation/docs-roq/content/2.6.2/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.6.2/guides/custom-operators.md b/documentation/docs-roq/content/2.6.2/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.6.2/guides/delaying-events.md b/documentation/docs-roq/content/2.6.2/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.6.2/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.6.2/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.6.2/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.6.2/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..660948a60 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/guides/emission-threads.md b/documentation/docs-roq/content/2.6.2/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.6.2/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.6.2/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/guides/filtering-items.md b/documentation/docs-roq/content/2.6.2/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.6.2/guides/framework-integration.md b/documentation/docs-roq/content/2.6.2/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.6.2/guides/handling-null.md b/documentation/docs-roq/content/2.6.2/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/guides/handling-timeouts.md b/documentation/docs-roq/content/2.6.2/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/guides/hot-streams.md b/documentation/docs-roq/content/2.6.2/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.6.2/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.6.2/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.6.2/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.6.2/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/guides/joining-unis.md b/documentation/docs-roq/content/2.6.2/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.6.2/guides/kotlin.md b/documentation/docs-roq/content/2.6.2/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.6.2/guides/logging.md b/documentation/docs-roq/content/2.6.2/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.6.2/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.6.2/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.6.2/guides/multi-split.md b/documentation/docs-roq/content/2.6.2/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.6.2/guides/pagination.md b/documentation/docs-roq/content/2.6.2/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/guides/polling.md b/documentation/docs-roq/content/2.6.2/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.6.2/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/guides/replaying-multis.md b/documentation/docs-roq/content/2.6.2/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/guides/rx.md b/documentation/docs-roq/content/2.6.2/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.6.2/guides/shortcut-methods.md b/documentation/docs-roq/content/2.6.2/guides/shortcut-methods.md new file mode 100644 index 000000000..1044a6c46 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onItemOrFailure().invoke((ignoredItem, ignoredException) -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onItemOrFailure().call((ignoredItem, ignoredException) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.6.2/guides/spies.md b/documentation/docs-roq/content/2.6.2/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/guides/take-skip-items.md b/documentation/docs-roq/content/2.6.2/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.6.2/guides/testing.md b/documentation/docs-roq/content/2.6.2/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.6.2/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.6.2/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/2.6.2/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/2.6.2/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.6.2/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.6.2/reference/publications.md b/documentation/docs-roq/content/2.6.2/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/reference/uni-and-multi.md b/documentation/docs-roq/content/2.6.2/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.6.2/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.6.2/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.6.2/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.6.2/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.6.2/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.6.2/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.6.2/tags-index.md b/documentation/docs-roq/content/2.6.2/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.6.2/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.6.2/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.6.2/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.6.2/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.6.2/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.6.2/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.6.2/tutorials/handling-failures.md b/documentation/docs-roq/content/2.6.2/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.6.2/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.6.2/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.6.2/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.6.2/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.6.2/tutorials/observing-events.md b/documentation/docs-roq/content/2.6.2/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.6.2/tutorials/retrying.md b/documentation/docs-roq/content/2.6.2/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.6.2/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.6.2/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.6.2/tutorials/transforming-items.md b/documentation/docs-roq/content/2.6.2/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.6.2/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.7.0/guides/branching.md b/documentation/docs-roq/content/2.7.0/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.7.0/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.7.0/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.7.0/guides/collecting-items.md b/documentation/docs-roq/content/2.7.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.7.0/guides/combining-items.md b/documentation/docs-roq/content/2.7.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.7.0/guides/completion-stage.md b/documentation/docs-roq/content/2.7.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.7.0/guides/context-passing.md b/documentation/docs-roq/content/2.7.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.7.0/guides/controlling-demand.md b/documentation/docs-roq/content/2.7.0/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.7.0/guides/converters.md b/documentation/docs-roq/content/2.7.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.7.0/guides/custom-operators.md b/documentation/docs-roq/content/2.7.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.7.0/guides/delaying-events.md b/documentation/docs-roq/content/2.7.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.7.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.7.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.7.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.7.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/guides/emission-threads.md b/documentation/docs-roq/content/2.7.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.7.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.7.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/guides/filtering-items.md b/documentation/docs-roq/content/2.7.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.7.0/guides/framework-integration.md b/documentation/docs-roq/content/2.7.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.7.0/guides/handling-null.md b/documentation/docs-roq/content/2.7.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/guides/handling-timeouts.md b/documentation/docs-roq/content/2.7.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/guides/hot-streams.md b/documentation/docs-roq/content/2.7.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.7.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.7.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.7.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.7.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/guides/joining-unis.md b/documentation/docs-roq/content/2.7.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.7.0/guides/kotlin.md b/documentation/docs-roq/content/2.7.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.7.0/guides/logging.md b/documentation/docs-roq/content/2.7.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.7.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.7.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.7.0/guides/multi-split.md b/documentation/docs-roq/content/2.7.0/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.7.0/guides/pagination.md b/documentation/docs-roq/content/2.7.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/guides/polling.md b/documentation/docs-roq/content/2.7.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.7.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/guides/replaying-multis.md b/documentation/docs-roq/content/2.7.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/guides/rx.md b/documentation/docs-roq/content/2.7.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.7.0/guides/shortcut-methods.md b/documentation/docs-roq/content/2.7.0/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.7.0/guides/spies.md b/documentation/docs-roq/content/2.7.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/guides/take-skip-items.md b/documentation/docs-roq/content/2.7.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.7.0/guides/testing.md b/documentation/docs-roq/content/2.7.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.7.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.7.0/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/2.7.0/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/2.7.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.7.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.7.0/reference/publications.md b/documentation/docs-roq/content/2.7.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/reference/uni-and-multi.md b/documentation/docs-roq/content/2.7.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.7.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.7.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.7.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.7.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.7.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.7.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.7.0/tags-index.md b/documentation/docs-roq/content/2.7.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.7.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.7.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.7.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.7.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.7.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.7.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.7.0/tutorials/handling-failures.md b/documentation/docs-roq/content/2.7.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.7.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.7.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.7.0/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.7.0/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.7.0/tutorials/observing-events.md b/documentation/docs-roq/content/2.7.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.7.0/tutorials/retrying.md b/documentation/docs-roq/content/2.7.0/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.7.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.7.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.7.0/tutorials/transforming-items.md b/documentation/docs-roq/content/2.7.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.7.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.8.0/guides/branching.md b/documentation/docs-roq/content/2.8.0/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.8.0/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.8.0/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.8.0/guides/collecting-items.md b/documentation/docs-roq/content/2.8.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.8.0/guides/combining-items.md b/documentation/docs-roq/content/2.8.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.8.0/guides/completion-stage.md b/documentation/docs-roq/content/2.8.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.8.0/guides/context-passing.md b/documentation/docs-roq/content/2.8.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.8.0/guides/controlling-demand.md b/documentation/docs-roq/content/2.8.0/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.8.0/guides/converters.md b/documentation/docs-roq/content/2.8.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.8.0/guides/custom-operators.md b/documentation/docs-roq/content/2.8.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.8.0/guides/delaying-events.md b/documentation/docs-roq/content/2.8.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.8.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.8.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.8.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.8.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/guides/emission-threads.md b/documentation/docs-roq/content/2.8.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.8.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.8.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/guides/filtering-items.md b/documentation/docs-roq/content/2.8.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.8.0/guides/framework-integration.md b/documentation/docs-roq/content/2.8.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.8.0/guides/handling-null.md b/documentation/docs-roq/content/2.8.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/guides/handling-timeouts.md b/documentation/docs-roq/content/2.8.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/guides/hot-streams.md b/documentation/docs-roq/content/2.8.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.8.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.8.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.8.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.8.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/guides/joining-unis.md b/documentation/docs-roq/content/2.8.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.8.0/guides/kotlin.md b/documentation/docs-roq/content/2.8.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.8.0/guides/logging.md b/documentation/docs-roq/content/2.8.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.8.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.8.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.8.0/guides/multi-split.md b/documentation/docs-roq/content/2.8.0/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.8.0/guides/pagination.md b/documentation/docs-roq/content/2.8.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/guides/polling.md b/documentation/docs-roq/content/2.8.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.8.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/guides/replaying-multis.md b/documentation/docs-roq/content/2.8.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/guides/rx.md b/documentation/docs-roq/content/2.8.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.8.0/guides/shortcut-methods.md b/documentation/docs-roq/content/2.8.0/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.8.0/guides/spies.md b/documentation/docs-roq/content/2.8.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/guides/take-skip-items.md b/documentation/docs-roq/content/2.8.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.8.0/guides/testing.md b/documentation/docs-roq/content/2.8.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.8.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.8.0/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/2.8.0/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/2.8.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.8.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.8.0/reference/publications.md b/documentation/docs-roq/content/2.8.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/reference/uni-and-multi.md b/documentation/docs-roq/content/2.8.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.8.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.8.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.8.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.8.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.8.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.8.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.8.0/tags-index.md b/documentation/docs-roq/content/2.8.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.8.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.8.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.8.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.8.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.8.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.8.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.8.0/tutorials/handling-failures.md b/documentation/docs-roq/content/2.8.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.8.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.8.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.8.0/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.8.0/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.8.0/tutorials/observing-events.md b/documentation/docs-roq/content/2.8.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.8.0/tutorials/retrying.md b/documentation/docs-roq/content/2.8.0/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.8.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.8.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.8.0/tutorials/transforming-items.md b/documentation/docs-roq/content/2.8.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.8.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.9.0/guides/branching.md b/documentation/docs-roq/content/2.9.0/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.9.0/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.9.0/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.9.0/guides/collecting-items.md b/documentation/docs-roq/content/2.9.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.9.0/guides/combining-items.md b/documentation/docs-roq/content/2.9.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.9.0/guides/completion-stage.md b/documentation/docs-roq/content/2.9.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.9.0/guides/context-passing.md b/documentation/docs-roq/content/2.9.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.9.0/guides/controlling-demand.md b/documentation/docs-roq/content/2.9.0/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.9.0/guides/converters.md b/documentation/docs-roq/content/2.9.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.9.0/guides/custom-operators.md b/documentation/docs-roq/content/2.9.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.9.0/guides/delaying-events.md b/documentation/docs-roq/content/2.9.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.9.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.9.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.9.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.9.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/guides/emission-threads.md b/documentation/docs-roq/content/2.9.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.9.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.9.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/guides/filtering-items.md b/documentation/docs-roq/content/2.9.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.9.0/guides/framework-integration.md b/documentation/docs-roq/content/2.9.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.9.0/guides/handling-null.md b/documentation/docs-roq/content/2.9.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/guides/handling-timeouts.md b/documentation/docs-roq/content/2.9.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/guides/hot-streams.md b/documentation/docs-roq/content/2.9.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.9.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.9.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.9.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.9.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/guides/joining-unis.md b/documentation/docs-roq/content/2.9.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.9.0/guides/kotlin.md b/documentation/docs-roq/content/2.9.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.9.0/guides/logging.md b/documentation/docs-roq/content/2.9.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.9.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.9.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.9.0/guides/multi-split.md b/documentation/docs-roq/content/2.9.0/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.9.0/guides/pagination.md b/documentation/docs-roq/content/2.9.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/guides/polling.md b/documentation/docs-roq/content/2.9.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.9.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/guides/replaying-multis.md b/documentation/docs-roq/content/2.9.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/guides/rx.md b/documentation/docs-roq/content/2.9.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.9.0/guides/shortcut-methods.md b/documentation/docs-roq/content/2.9.0/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.9.0/guides/spies.md b/documentation/docs-roq/content/2.9.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/guides/take-skip-items.md b/documentation/docs-roq/content/2.9.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.9.0/guides/testing.md b/documentation/docs-roq/content/2.9.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.9.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.9.0/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/2.9.0/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/2.9.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.9.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.9.0/reference/publications.md b/documentation/docs-roq/content/2.9.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/reference/uni-and-multi.md b/documentation/docs-roq/content/2.9.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.9.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.9.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.9.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.9.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.9.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.9.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.9.0/tags-index.md b/documentation/docs-roq/content/2.9.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.9.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.9.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.9.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.9.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.9.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.9.0/tutorials/handling-failures.md b/documentation/docs-roq/content/2.9.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.9.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.9.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.9.0/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.9.0/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.9.0/tutorials/observing-events.md b/documentation/docs-roq/content/2.9.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.9.0/tutorials/retrying.md b/documentation/docs-roq/content/2.9.0/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.9.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.9.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.9.0/tutorials/transforming-items.md b/documentation/docs-roq/content/2.9.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.9.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.9.1/guides/branching.md b/documentation/docs-roq/content/2.9.1/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.9.1/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.9.1/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.9.1/guides/collecting-items.md b/documentation/docs-roq/content/2.9.1/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.9.1/guides/combining-items.md b/documentation/docs-roq/content/2.9.1/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.9.1/guides/completion-stage.md b/documentation/docs-roq/content/2.9.1/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.9.1/guides/context-passing.md b/documentation/docs-roq/content/2.9.1/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.9.1/guides/controlling-demand.md b/documentation/docs-roq/content/2.9.1/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.9.1/guides/converters.md b/documentation/docs-roq/content/2.9.1/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.9.1/guides/custom-operators.md b/documentation/docs-roq/content/2.9.1/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.9.1/guides/delaying-events.md b/documentation/docs-roq/content/2.9.1/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.9.1/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.9.1/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.9.1/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.9.1/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/guides/emission-threads.md b/documentation/docs-roq/content/2.9.1/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.9.1/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.9.1/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/guides/filtering-items.md b/documentation/docs-roq/content/2.9.1/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.9.1/guides/framework-integration.md b/documentation/docs-roq/content/2.9.1/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.9.1/guides/handling-null.md b/documentation/docs-roq/content/2.9.1/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/guides/handling-timeouts.md b/documentation/docs-roq/content/2.9.1/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/guides/hot-streams.md b/documentation/docs-roq/content/2.9.1/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.9.1/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.9.1/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.9.1/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.9.1/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/guides/joining-unis.md b/documentation/docs-roq/content/2.9.1/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.9.1/guides/kotlin.md b/documentation/docs-roq/content/2.9.1/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.9.1/guides/logging.md b/documentation/docs-roq/content/2.9.1/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.9.1/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.9.1/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.9.1/guides/multi-split.md b/documentation/docs-roq/content/2.9.1/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.9.1/guides/pagination.md b/documentation/docs-roq/content/2.9.1/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/guides/polling.md b/documentation/docs-roq/content/2.9.1/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.9.1/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/guides/replaying-multis.md b/documentation/docs-roq/content/2.9.1/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/guides/rx.md b/documentation/docs-roq/content/2.9.1/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.9.1/guides/shortcut-methods.md b/documentation/docs-roq/content/2.9.1/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.9.1/guides/spies.md b/documentation/docs-roq/content/2.9.1/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/guides/take-skip-items.md b/documentation/docs-roq/content/2.9.1/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.9.1/guides/testing.md b/documentation/docs-roq/content/2.9.1/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.9.1/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.9.1/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/2.9.1/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/2.9.1/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.9.1/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.9.1/reference/publications.md b/documentation/docs-roq/content/2.9.1/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/reference/uni-and-multi.md b/documentation/docs-roq/content/2.9.1/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.9.1/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.9.1/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.9.1/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.9.1/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.9.1/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.9.1/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.9.1/tags-index.md b/documentation/docs-roq/content/2.9.1/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.1/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.9.1/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.9.1/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.9.1/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.9.1/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.9.1/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.9.1/tutorials/handling-failures.md b/documentation/docs-roq/content/2.9.1/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.9.1/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.9.1/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.9.1/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.9.1/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.9.1/tutorials/observing-events.md b/documentation/docs-roq/content/2.9.1/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.9.1/tutorials/retrying.md b/documentation/docs-roq/content/2.9.1/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.9.1/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.9.1/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.9.1/tutorials/transforming-items.md b/documentation/docs-roq/content/2.9.1/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.9.1/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.9.2/guides/branching.md b/documentation/docs-roq/content/2.9.2/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.9.2/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.9.2/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.9.2/guides/collecting-items.md b/documentation/docs-roq/content/2.9.2/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.9.2/guides/combining-items.md b/documentation/docs-roq/content/2.9.2/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.9.2/guides/completion-stage.md b/documentation/docs-roq/content/2.9.2/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.9.2/guides/context-passing.md b/documentation/docs-roq/content/2.9.2/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.9.2/guides/controlling-demand.md b/documentation/docs-roq/content/2.9.2/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.9.2/guides/converters.md b/documentation/docs-roq/content/2.9.2/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.9.2/guides/custom-operators.md b/documentation/docs-roq/content/2.9.2/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.9.2/guides/delaying-events.md b/documentation/docs-roq/content/2.9.2/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.9.2/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.9.2/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.9.2/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.9.2/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/guides/emission-threads.md b/documentation/docs-roq/content/2.9.2/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.9.2/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.9.2/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/guides/filtering-items.md b/documentation/docs-roq/content/2.9.2/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.9.2/guides/framework-integration.md b/documentation/docs-roq/content/2.9.2/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.9.2/guides/handling-null.md b/documentation/docs-roq/content/2.9.2/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/guides/handling-timeouts.md b/documentation/docs-roq/content/2.9.2/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/guides/hot-streams.md b/documentation/docs-roq/content/2.9.2/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.9.2/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.9.2/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.9.2/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.9.2/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/guides/joining-unis.md b/documentation/docs-roq/content/2.9.2/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.9.2/guides/kotlin.md b/documentation/docs-roq/content/2.9.2/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.9.2/guides/logging.md b/documentation/docs-roq/content/2.9.2/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.9.2/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.9.2/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.9.2/guides/multi-split.md b/documentation/docs-roq/content/2.9.2/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.9.2/guides/pagination.md b/documentation/docs-roq/content/2.9.2/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/guides/polling.md b/documentation/docs-roq/content/2.9.2/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.9.2/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/guides/replaying-multis.md b/documentation/docs-roq/content/2.9.2/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/guides/rx.md b/documentation/docs-roq/content/2.9.2/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.9.2/guides/shortcut-methods.md b/documentation/docs-roq/content/2.9.2/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.9.2/guides/spies.md b/documentation/docs-roq/content/2.9.2/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/guides/take-skip-items.md b/documentation/docs-roq/content/2.9.2/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.9.2/guides/testing.md b/documentation/docs-roq/content/2.9.2/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.9.2/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.9.2/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/2.9.2/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/2.9.2/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.9.2/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.9.2/reference/publications.md b/documentation/docs-roq/content/2.9.2/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/reference/uni-and-multi.md b/documentation/docs-roq/content/2.9.2/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.9.2/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.9.2/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.9.2/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.9.2/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.9.2/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.9.2/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.9.2/tags-index.md b/documentation/docs-roq/content/2.9.2/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.2/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.9.2/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.9.2/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.9.2/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.9.2/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.9.2/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.9.2/tutorials/handling-failures.md b/documentation/docs-roq/content/2.9.2/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.9.2/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.9.2/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.9.2/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.9.2/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.9.2/tutorials/observing-events.md b/documentation/docs-roq/content/2.9.2/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.9.2/tutorials/retrying.md b/documentation/docs-roq/content/2.9.2/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.9.2/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.9.2/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.9.2/tutorials/transforming-items.md b/documentation/docs-roq/content/2.9.2/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.9.2/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.9.3/guides/branching.md b/documentation/docs-roq/content/2.9.3/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.9.3/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.9.3/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.9.3/guides/collecting-items.md b/documentation/docs-roq/content/2.9.3/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.9.3/guides/combining-items.md b/documentation/docs-roq/content/2.9.3/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.9.3/guides/completion-stage.md b/documentation/docs-roq/content/2.9.3/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.9.3/guides/context-passing.md b/documentation/docs-roq/content/2.9.3/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.9.3/guides/controlling-demand.md b/documentation/docs-roq/content/2.9.3/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.9.3/guides/converters.md b/documentation/docs-roq/content/2.9.3/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.9.3/guides/custom-operators.md b/documentation/docs-roq/content/2.9.3/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.9.3/guides/delaying-events.md b/documentation/docs-roq/content/2.9.3/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.9.3/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.9.3/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.9.3/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.9.3/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/guides/emission-threads.md b/documentation/docs-roq/content/2.9.3/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.9.3/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.9.3/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/guides/filtering-items.md b/documentation/docs-roq/content/2.9.3/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.9.3/guides/framework-integration.md b/documentation/docs-roq/content/2.9.3/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.9.3/guides/handling-null.md b/documentation/docs-roq/content/2.9.3/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/guides/handling-timeouts.md b/documentation/docs-roq/content/2.9.3/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/guides/hot-streams.md b/documentation/docs-roq/content/2.9.3/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.9.3/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.9.3/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.9.3/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.9.3/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/guides/joining-unis.md b/documentation/docs-roq/content/2.9.3/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.9.3/guides/kotlin.md b/documentation/docs-roq/content/2.9.3/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.9.3/guides/logging.md b/documentation/docs-roq/content/2.9.3/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.9.3/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.9.3/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.9.3/guides/multi-split.md b/documentation/docs-roq/content/2.9.3/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.9.3/guides/pagination.md b/documentation/docs-roq/content/2.9.3/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/guides/polling.md b/documentation/docs-roq/content/2.9.3/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.9.3/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/guides/replaying-multis.md b/documentation/docs-roq/content/2.9.3/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/guides/rx.md b/documentation/docs-roq/content/2.9.3/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.9.3/guides/shortcut-methods.md b/documentation/docs-roq/content/2.9.3/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.9.3/guides/spies.md b/documentation/docs-roq/content/2.9.3/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/guides/take-skip-items.md b/documentation/docs-roq/content/2.9.3/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.9.3/guides/testing.md b/documentation/docs-roq/content/2.9.3/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.9.3/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.9.3/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/2.9.3/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/2.9.3/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.9.3/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.9.3/reference/publications.md b/documentation/docs-roq/content/2.9.3/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/reference/uni-and-multi.md b/documentation/docs-roq/content/2.9.3/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.9.3/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.9.3/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.9.3/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.9.3/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.9.3/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.9.3/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.9.3/tags-index.md b/documentation/docs-roq/content/2.9.3/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.3/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.9.3/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.9.3/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.9.3/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.9.3/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.9.3/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.9.3/tutorials/handling-failures.md b/documentation/docs-roq/content/2.9.3/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.9.3/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.9.3/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.9.3/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.9.3/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.9.3/tutorials/observing-events.md b/documentation/docs-roq/content/2.9.3/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.9.3/tutorials/retrying.md b/documentation/docs-roq/content/2.9.3/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.9.3/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.9.3/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.9.3/tutorials/transforming-items.md b/documentation/docs-roq/content/2.9.3/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.9.3/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.9.4/guides/branching.md b/documentation/docs-roq/content/2.9.4/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.9.4/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.9.4/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.9.4/guides/collecting-items.md b/documentation/docs-roq/content/2.9.4/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.9.4/guides/combining-items.md b/documentation/docs-roq/content/2.9.4/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.9.4/guides/completion-stage.md b/documentation/docs-roq/content/2.9.4/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.9.4/guides/context-passing.md b/documentation/docs-roq/content/2.9.4/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.9.4/guides/controlling-demand.md b/documentation/docs-roq/content/2.9.4/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.9.4/guides/converters.md b/documentation/docs-roq/content/2.9.4/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.9.4/guides/custom-operators.md b/documentation/docs-roq/content/2.9.4/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.9.4/guides/delaying-events.md b/documentation/docs-roq/content/2.9.4/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.9.4/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.9.4/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.9.4/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.9.4/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/guides/emission-threads.md b/documentation/docs-roq/content/2.9.4/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.9.4/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.9.4/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/guides/filtering-items.md b/documentation/docs-roq/content/2.9.4/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.9.4/guides/framework-integration.md b/documentation/docs-roq/content/2.9.4/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.9.4/guides/handling-null.md b/documentation/docs-roq/content/2.9.4/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/guides/handling-timeouts.md b/documentation/docs-roq/content/2.9.4/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/guides/hot-streams.md b/documentation/docs-roq/content/2.9.4/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.9.4/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.9.4/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.9.4/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.9.4/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/guides/joining-unis.md b/documentation/docs-roq/content/2.9.4/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.9.4/guides/kotlin.md b/documentation/docs-roq/content/2.9.4/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.9.4/guides/logging.md b/documentation/docs-roq/content/2.9.4/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.9.4/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.9.4/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.9.4/guides/multi-split.md b/documentation/docs-roq/content/2.9.4/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.9.4/guides/pagination.md b/documentation/docs-roq/content/2.9.4/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/guides/polling.md b/documentation/docs-roq/content/2.9.4/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.9.4/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/guides/replaying-multis.md b/documentation/docs-roq/content/2.9.4/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/guides/rx.md b/documentation/docs-roq/content/2.9.4/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.9.4/guides/shortcut-methods.md b/documentation/docs-roq/content/2.9.4/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.9.4/guides/spies.md b/documentation/docs-roq/content/2.9.4/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/guides/take-skip-items.md b/documentation/docs-roq/content/2.9.4/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.9.4/guides/testing.md b/documentation/docs-roq/content/2.9.4/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.9.4/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.9.4/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/2.9.4/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/2.9.4/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.9.4/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.9.4/reference/publications.md b/documentation/docs-roq/content/2.9.4/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/reference/uni-and-multi.md b/documentation/docs-roq/content/2.9.4/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.9.4/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.9.4/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.9.4/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.9.4/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.9.4/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.9.4/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.9.4/tags-index.md b/documentation/docs-roq/content/2.9.4/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.4/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.9.4/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.9.4/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.9.4/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.9.4/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.9.4/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.9.4/tutorials/handling-failures.md b/documentation/docs-roq/content/2.9.4/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.9.4/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.9.4/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.9.4/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.9.4/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.9.4/tutorials/observing-events.md b/documentation/docs-roq/content/2.9.4/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.9.4/tutorials/retrying.md b/documentation/docs-roq/content/2.9.4/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.9.4/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.9.4/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.9.4/tutorials/transforming-items.md b/documentation/docs-roq/content/2.9.4/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.9.4/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/2.9.5/guides/branching.md b/documentation/docs-roq/content/2.9.5/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/2.9.5/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/2.9.5/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/2.9.5/guides/collecting-items.md b/documentation/docs-roq/content/2.9.5/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/2.9.5/guides/combining-items.md b/documentation/docs-roq/content/2.9.5/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/2.9.5/guides/completion-stage.md b/documentation/docs-roq/content/2.9.5/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/2.9.5/guides/context-passing.md b/documentation/docs-roq/content/2.9.5/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/2.9.5/guides/controlling-demand.md b/documentation/docs-roq/content/2.9.5/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/2.9.5/guides/converters.md b/documentation/docs-roq/content/2.9.5/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/2.9.5/guides/custom-operators.md b/documentation/docs-roq/content/2.9.5/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/2.9.5/guides/delaying-events.md b/documentation/docs-roq/content/2.9.5/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/2.9.5/guides/dropped-exceptions.md b/documentation/docs-roq/content/2.9.5/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/2.9.5/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/2.9.5/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/guides/emission-threads.md b/documentation/docs-roq/content/2.9.5/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/2.9.5/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/2.9.5/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/guides/filtering-items.md b/documentation/docs-roq/content/2.9.5/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/2.9.5/guides/framework-integration.md b/documentation/docs-roq/content/2.9.5/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/2.9.5/guides/handling-null.md b/documentation/docs-roq/content/2.9.5/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/guides/handling-timeouts.md b/documentation/docs-roq/content/2.9.5/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/guides/hot-streams.md b/documentation/docs-roq/content/2.9.5/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/2.9.5/guides/imperative-to-reactive.md b/documentation/docs-roq/content/2.9.5/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/2.9.5/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/2.9.5/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/guides/joining-unis.md b/documentation/docs-roq/content/2.9.5/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/2.9.5/guides/kotlin.md b/documentation/docs-roq/content/2.9.5/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/2.9.5/guides/logging.md b/documentation/docs-roq/content/2.9.5/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/2.9.5/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/2.9.5/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/2.9.5/guides/multi-split.md b/documentation/docs-roq/content/2.9.5/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/2.9.5/guides/pagination.md b/documentation/docs-roq/content/2.9.5/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/guides/polling.md b/documentation/docs-roq/content/2.9.5/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/guides/reactive-to-imperative.md b/documentation/docs-roq/content/2.9.5/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/guides/replaying-multis.md b/documentation/docs-roq/content/2.9.5/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/guides/rx.md b/documentation/docs-roq/content/2.9.5/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/2.9.5/guides/shortcut-methods.md b/documentation/docs-roq/content/2.9.5/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/2.9.5/guides/spies.md b/documentation/docs-roq/content/2.9.5/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/guides/take-skip-items.md b/documentation/docs-roq/content/2.9.5/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/2.9.5/guides/testing.md b/documentation/docs-roq/content/2.9.5/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/guides/unchecked-exceptions.md b/documentation/docs-roq/content/2.9.5/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/2.9.5/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/2.9.5/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/2.9.5/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/2.9.5/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/2.9.5/reference/publications.md b/documentation/docs-roq/content/2.9.5/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/reference/uni-and-multi.md b/documentation/docs-roq/content/2.9.5/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/2.9.5/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/2.9.5/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/2.9.5/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/2.9.5/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/2.9.5/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/2.9.5/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/2.9.5/tags-index.md b/documentation/docs-roq/content/2.9.5/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/2.9.5/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/2.9.5/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/2.9.5/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/2.9.5/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/2.9.5/tutorials/getting-mutiny.md b/documentation/docs-roq/content/2.9.5/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/2.9.5/tutorials/handling-failures.md b/documentation/docs-roq/content/2.9.5/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/2.9.5/tutorials/hello-mutiny.md b/documentation/docs-roq/content/2.9.5/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/2.9.5/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/2.9.5/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/2.9.5/tutorials/observing-events.md b/documentation/docs-roq/content/2.9.5/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/2.9.5/tutorials/retrying.md b/documentation/docs-roq/content/2.9.5/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/2.9.5/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/2.9.5/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/2.9.5/tutorials/transforming-items.md b/documentation/docs-roq/content/2.9.5/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/2.9.5/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/3.0.0/guides/branching.md b/documentation/docs-roq/content/3.0.0/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/3.0.0/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/3.0.0/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/3.0.0/guides/collecting-items.md b/documentation/docs-roq/content/3.0.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/3.0.0/guides/combining-items.md b/documentation/docs-roq/content/3.0.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/3.0.0/guides/completion-stage.md b/documentation/docs-roq/content/3.0.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/3.0.0/guides/context-passing.md b/documentation/docs-roq/content/3.0.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/3.0.0/guides/controlling-demand.md b/documentation/docs-roq/content/3.0.0/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/3.0.0/guides/converters.md b/documentation/docs-roq/content/3.0.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/3.0.0/guides/custom-operators.md b/documentation/docs-roq/content/3.0.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/3.0.0/guides/delaying-events.md b/documentation/docs-roq/content/3.0.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/3.0.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/3.0.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/3.0.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/3.0.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/guides/emission-threads.md b/documentation/docs-roq/content/3.0.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/3.0.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/3.0.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/guides/filtering-items.md b/documentation/docs-roq/content/3.0.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/3.0.0/guides/framework-integration.md b/documentation/docs-roq/content/3.0.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/3.0.0/guides/handling-null.md b/documentation/docs-roq/content/3.0.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/guides/handling-timeouts.md b/documentation/docs-roq/content/3.0.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/guides/hot-streams.md b/documentation/docs-roq/content/3.0.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/3.0.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/3.0.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/3.0.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/3.0.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/guides/joining-unis.md b/documentation/docs-roq/content/3.0.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/3.0.0/guides/kotlin.md b/documentation/docs-roq/content/3.0.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/3.0.0/guides/logging.md b/documentation/docs-roq/content/3.0.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/3.0.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/3.0.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/3.0.0/guides/multi-split.md b/documentation/docs-roq/content/3.0.0/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/3.0.0/guides/pagination.md b/documentation/docs-roq/content/3.0.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/guides/polling.md b/documentation/docs-roq/content/3.0.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/3.0.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/guides/replaying-multis.md b/documentation/docs-roq/content/3.0.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/guides/rx.md b/documentation/docs-roq/content/3.0.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/3.0.0/guides/shortcut-methods.md b/documentation/docs-roq/content/3.0.0/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/3.0.0/guides/spies.md b/documentation/docs-roq/content/3.0.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/guides/take-skip-items.md b/documentation/docs-roq/content/3.0.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/3.0.0/guides/testing.md b/documentation/docs-roq/content/3.0.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/3.0.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/3.0.0/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/3.0.0/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/3.0.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/3.0.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/3.0.0/reference/publications.md b/documentation/docs-roq/content/3.0.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/reference/uni-and-multi.md b/documentation/docs-roq/content/3.0.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/3.0.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/3.0.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/3.0.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/3.0.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/3.0.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/3.0.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/3.0.0/tags-index.md b/documentation/docs-roq/content/3.0.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/3.0.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/3.0.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/3.0.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/3.0.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/3.0.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/3.0.0/tutorials/handling-failures.md b/documentation/docs-roq/content/3.0.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/3.0.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/3.0.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/3.0.0/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/3.0.0/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/3.0.0/tutorials/observing-events.md b/documentation/docs-roq/content/3.0.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/3.0.0/tutorials/retrying.md b/documentation/docs-roq/content/3.0.0/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/3.0.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/3.0.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/3.0.0/tutorials/transforming-items.md b/documentation/docs-roq/content/3.0.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/3.0.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/3.0.1/guides/branching.md b/documentation/docs-roq/content/3.0.1/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/3.0.1/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/3.0.1/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/3.0.1/guides/collecting-items.md b/documentation/docs-roq/content/3.0.1/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/3.0.1/guides/combining-items.md b/documentation/docs-roq/content/3.0.1/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/3.0.1/guides/completion-stage.md b/documentation/docs-roq/content/3.0.1/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/3.0.1/guides/context-passing.md b/documentation/docs-roq/content/3.0.1/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/3.0.1/guides/controlling-demand.md b/documentation/docs-roq/content/3.0.1/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/3.0.1/guides/converters.md b/documentation/docs-roq/content/3.0.1/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/3.0.1/guides/custom-operators.md b/documentation/docs-roq/content/3.0.1/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/3.0.1/guides/delaying-events.md b/documentation/docs-roq/content/3.0.1/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/3.0.1/guides/dropped-exceptions.md b/documentation/docs-roq/content/3.0.1/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/3.0.1/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/3.0.1/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/guides/emission-threads.md b/documentation/docs-roq/content/3.0.1/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/3.0.1/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/3.0.1/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/guides/filtering-items.md b/documentation/docs-roq/content/3.0.1/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/3.0.1/guides/framework-integration.md b/documentation/docs-roq/content/3.0.1/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/3.0.1/guides/handling-null.md b/documentation/docs-roq/content/3.0.1/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/guides/handling-timeouts.md b/documentation/docs-roq/content/3.0.1/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/guides/hot-streams.md b/documentation/docs-roq/content/3.0.1/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/3.0.1/guides/imperative-to-reactive.md b/documentation/docs-roq/content/3.0.1/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/3.0.1/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/3.0.1/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/guides/joining-unis.md b/documentation/docs-roq/content/3.0.1/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/3.0.1/guides/kotlin.md b/documentation/docs-roq/content/3.0.1/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/3.0.1/guides/logging.md b/documentation/docs-roq/content/3.0.1/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/3.0.1/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/3.0.1/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/3.0.1/guides/multi-split.md b/documentation/docs-roq/content/3.0.1/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/3.0.1/guides/pagination.md b/documentation/docs-roq/content/3.0.1/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/guides/polling.md b/documentation/docs-roq/content/3.0.1/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/guides/reactive-to-imperative.md b/documentation/docs-roq/content/3.0.1/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/guides/replaying-multis.md b/documentation/docs-roq/content/3.0.1/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/guides/rx.md b/documentation/docs-roq/content/3.0.1/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/3.0.1/guides/shortcut-methods.md b/documentation/docs-roq/content/3.0.1/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/3.0.1/guides/spies.md b/documentation/docs-roq/content/3.0.1/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/guides/take-skip-items.md b/documentation/docs-roq/content/3.0.1/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/3.0.1/guides/testing.md b/documentation/docs-roq/content/3.0.1/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/guides/unchecked-exceptions.md b/documentation/docs-roq/content/3.0.1/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/3.0.1/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/3.0.1/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/3.0.1/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/3.0.1/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/3.0.1/reference/publications.md b/documentation/docs-roq/content/3.0.1/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/reference/uni-and-multi.md b/documentation/docs-roq/content/3.0.1/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/3.0.1/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/3.0.1/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/3.0.1/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/3.0.1/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/3.0.1/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/3.0.1/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/3.0.1/tags-index.md b/documentation/docs-roq/content/3.0.1/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.1/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/3.0.1/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/3.0.1/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/3.0.1/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/3.0.1/tutorials/getting-mutiny.md b/documentation/docs-roq/content/3.0.1/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/3.0.1/tutorials/handling-failures.md b/documentation/docs-roq/content/3.0.1/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/3.0.1/tutorials/hello-mutiny.md b/documentation/docs-roq/content/3.0.1/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/3.0.1/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/3.0.1/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/3.0.1/tutorials/observing-events.md b/documentation/docs-roq/content/3.0.1/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/3.0.1/tutorials/retrying.md b/documentation/docs-roq/content/3.0.1/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/3.0.1/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/3.0.1/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/3.0.1/tutorials/transforming-items.md b/documentation/docs-roq/content/3.0.1/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/3.0.1/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/3.0.2/guides/branching.md b/documentation/docs-roq/content/3.0.2/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/3.0.2/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/3.0.2/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/3.0.2/guides/collecting-items.md b/documentation/docs-roq/content/3.0.2/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/3.0.2/guides/combining-items.md b/documentation/docs-roq/content/3.0.2/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/3.0.2/guides/completion-stage.md b/documentation/docs-roq/content/3.0.2/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/3.0.2/guides/context-passing.md b/documentation/docs-roq/content/3.0.2/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/3.0.2/guides/controlling-demand.md b/documentation/docs-roq/content/3.0.2/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/3.0.2/guides/converters.md b/documentation/docs-roq/content/3.0.2/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/3.0.2/guides/custom-operators.md b/documentation/docs-roq/content/3.0.2/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/3.0.2/guides/delaying-events.md b/documentation/docs-roq/content/3.0.2/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/3.0.2/guides/dropped-exceptions.md b/documentation/docs-roq/content/3.0.2/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/3.0.2/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/3.0.2/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/guides/emission-threads.md b/documentation/docs-roq/content/3.0.2/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/3.0.2/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/3.0.2/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/guides/filtering-items.md b/documentation/docs-roq/content/3.0.2/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/3.0.2/guides/framework-integration.md b/documentation/docs-roq/content/3.0.2/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/3.0.2/guides/handling-null.md b/documentation/docs-roq/content/3.0.2/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/guides/handling-timeouts.md b/documentation/docs-roq/content/3.0.2/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/guides/hot-streams.md b/documentation/docs-roq/content/3.0.2/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/3.0.2/guides/imperative-to-reactive.md b/documentation/docs-roq/content/3.0.2/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/3.0.2/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/3.0.2/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/guides/joining-unis.md b/documentation/docs-roq/content/3.0.2/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/3.0.2/guides/kotlin.md b/documentation/docs-roq/content/3.0.2/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/3.0.2/guides/logging.md b/documentation/docs-roq/content/3.0.2/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/3.0.2/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/3.0.2/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/3.0.2/guides/multi-split.md b/documentation/docs-roq/content/3.0.2/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/3.0.2/guides/pagination.md b/documentation/docs-roq/content/3.0.2/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/guides/polling.md b/documentation/docs-roq/content/3.0.2/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/guides/reactive-to-imperative.md b/documentation/docs-roq/content/3.0.2/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/guides/replaying-multis.md b/documentation/docs-roq/content/3.0.2/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/guides/rx.md b/documentation/docs-roq/content/3.0.2/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/3.0.2/guides/shortcut-methods.md b/documentation/docs-roq/content/3.0.2/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/3.0.2/guides/spies.md b/documentation/docs-roq/content/3.0.2/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/guides/take-skip-items.md b/documentation/docs-roq/content/3.0.2/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/3.0.2/guides/testing.md b/documentation/docs-roq/content/3.0.2/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/guides/unchecked-exceptions.md b/documentation/docs-roq/content/3.0.2/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/3.0.2/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/3.0.2/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/3.0.2/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/3.0.2/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/3.0.2/reference/publications.md b/documentation/docs-roq/content/3.0.2/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/reference/uni-and-multi.md b/documentation/docs-roq/content/3.0.2/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/3.0.2/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/3.0.2/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/3.0.2/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/3.0.2/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/3.0.2/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/3.0.2/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/3.0.2/tags-index.md b/documentation/docs-roq/content/3.0.2/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.2/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/3.0.2/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/3.0.2/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/3.0.2/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/3.0.2/tutorials/getting-mutiny.md b/documentation/docs-roq/content/3.0.2/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/3.0.2/tutorials/handling-failures.md b/documentation/docs-roq/content/3.0.2/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/3.0.2/tutorials/hello-mutiny.md b/documentation/docs-roq/content/3.0.2/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/3.0.2/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/3.0.2/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/3.0.2/tutorials/observing-events.md b/documentation/docs-roq/content/3.0.2/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/3.0.2/tutorials/retrying.md b/documentation/docs-roq/content/3.0.2/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/3.0.2/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/3.0.2/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/3.0.2/tutorials/transforming-items.md b/documentation/docs-roq/content/3.0.2/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/3.0.2/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/3.0.3/guides/branching.md b/documentation/docs-roq/content/3.0.3/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/3.0.3/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/3.0.3/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/3.0.3/guides/collecting-items.md b/documentation/docs-roq/content/3.0.3/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/3.0.3/guides/combining-items.md b/documentation/docs-roq/content/3.0.3/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/3.0.3/guides/completion-stage.md b/documentation/docs-roq/content/3.0.3/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/3.0.3/guides/context-passing.md b/documentation/docs-roq/content/3.0.3/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/3.0.3/guides/controlling-demand.md b/documentation/docs-roq/content/3.0.3/guides/controlling-demand.md new file mode 100644 index 000000000..dde1b6ce3 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/controlling-demand.md @@ -0,0 +1,57 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. diff --git a/documentation/docs-roq/content/3.0.3/guides/converters.md b/documentation/docs-roq/content/3.0.3/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/3.0.3/guides/custom-operators.md b/documentation/docs-roq/content/3.0.3/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/3.0.3/guides/delaying-events.md b/documentation/docs-roq/content/3.0.3/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/3.0.3/guides/dropped-exceptions.md b/documentation/docs-roq/content/3.0.3/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/3.0.3/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/3.0.3/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/guides/emission-threads.md b/documentation/docs-roq/content/3.0.3/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/3.0.3/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/3.0.3/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/guides/filtering-items.md b/documentation/docs-roq/content/3.0.3/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/3.0.3/guides/framework-integration.md b/documentation/docs-roq/content/3.0.3/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/3.0.3/guides/handling-null.md b/documentation/docs-roq/content/3.0.3/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/guides/handling-timeouts.md b/documentation/docs-roq/content/3.0.3/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/guides/hot-streams.md b/documentation/docs-roq/content/3.0.3/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/3.0.3/guides/imperative-to-reactive.md b/documentation/docs-roq/content/3.0.3/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/3.0.3/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/3.0.3/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/guides/joining-unis.md b/documentation/docs-roq/content/3.0.3/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/3.0.3/guides/kotlin.md b/documentation/docs-roq/content/3.0.3/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/3.0.3/guides/logging.md b/documentation/docs-roq/content/3.0.3/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/3.0.3/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/3.0.3/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/3.0.3/guides/multi-split.md b/documentation/docs-roq/content/3.0.3/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/3.0.3/guides/pagination.md b/documentation/docs-roq/content/3.0.3/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/guides/polling.md b/documentation/docs-roq/content/3.0.3/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/guides/reactive-to-imperative.md b/documentation/docs-roq/content/3.0.3/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/guides/replaying-multis.md b/documentation/docs-roq/content/3.0.3/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/guides/rx.md b/documentation/docs-roq/content/3.0.3/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/3.0.3/guides/shortcut-methods.md b/documentation/docs-roq/content/3.0.3/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/3.0.3/guides/spies.md b/documentation/docs-roq/content/3.0.3/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/guides/take-skip-items.md b/documentation/docs-roq/content/3.0.3/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/3.0.3/guides/testing.md b/documentation/docs-roq/content/3.0.3/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/guides/unchecked-exceptions.md b/documentation/docs-roq/content/3.0.3/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/3.0.3/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/3.0.3/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/3.0.3/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/3.0.3/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/3.0.3/reference/publications.md b/documentation/docs-roq/content/3.0.3/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/reference/uni-and-multi.md b/documentation/docs-roq/content/3.0.3/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/3.0.3/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/3.0.3/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/3.0.3/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/3.0.3/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/3.0.3/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/3.0.3/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/3.0.3/tags-index.md b/documentation/docs-roq/content/3.0.3/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/3.0.3/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/3.0.3/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/3.0.3/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/3.0.3/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/3.0.3/tutorials/getting-mutiny.md b/documentation/docs-roq/content/3.0.3/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/3.0.3/tutorials/handling-failures.md b/documentation/docs-roq/content/3.0.3/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/3.0.3/tutorials/hello-mutiny.md b/documentation/docs-roq/content/3.0.3/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/3.0.3/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/3.0.3/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/3.0.3/tutorials/observing-events.md b/documentation/docs-roq/content/3.0.3/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/3.0.3/tutorials/retrying.md b/documentation/docs-roq/content/3.0.3/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/3.0.3/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/3.0.3/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/3.0.3/tutorials/transforming-items.md b/documentation/docs-roq/content/3.0.3/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/3.0.3/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/3.1.0/guides/branching.md b/documentation/docs-roq/content/3.1.0/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/3.1.0/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/3.1.0/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/3.1.0/guides/collecting-items.md b/documentation/docs-roq/content/3.1.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/3.1.0/guides/combining-items.md b/documentation/docs-roq/content/3.1.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/3.1.0/guides/completion-stage.md b/documentation/docs-roq/content/3.1.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/3.1.0/guides/context-passing.md b/documentation/docs-roq/content/3.1.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/3.1.0/guides/controlling-demand.md b/documentation/docs-roq/content/3.1.0/guides/controlling-demand.md new file mode 100644 index 000000000..ed21844c3 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/controlling-demand.md @@ -0,0 +1,155 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. + +## Pausing the demand + +The `Multi.pauseDemand()` operator provides fine-grained control over demand propagation in reactive streams. +Unlike cancellation, which terminates the subscription, pausing allows to suspend demand without unsubscribing from the upstream. +This is useful for implementing flow control patterns where item flow needs to be paused based on external conditions. + +### Basic pausing and resuming + +The `pauseDemand()` operator works with a `DemandPauser` handle that allows to control the stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "basic")} +``` + +The `DemandPauser` provides methods to: + +- `pause()`: Stop propagating demand to upstream +- `resume()`: Resume demand propagation and deliver buffered items +- `isPaused()`: Check the current pause state + +Note that a few items may still arrive after pausing due to in-flight requests that were already issued to upstream. + +### Starting in a paused state + +You can create a stream that starts paused and only begins flowing when explicitly resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "initially-paused")} +``` + +This is useful when you want to prepare a stream but delay its execution until certain conditions are met. + +### Late subscription + +By default, the upstream subscription happens immediately even when starting paused. +The `lateSubscription()` option delays the upstream subscription until the stream is resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "late-subscription")} +``` + +### Buffer strategies + +When a stream is paused, the operator stops requesting new items from upstream. +However, items that were already requested (due to downstream demand) may still arrive. +Buffer strategies control what happens to these in-flight items. + +The `pauseDemand()` operator supports three buffer strategies: `BUFFER` (default), `DROP`, and `IGNORE`. +Configuring any other strategy will throw an `IllegalArgumentException`. + +#### BUFFER strategy (default) + +Already-requested items are buffered while paused and delivered when resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "buffer-strategy")} +``` + +You can configure the buffer size: + +- `bufferUnconditionally()`: Unbounded buffer +- `bufferSize(n)`: Buffer up to `n` items, then fail with buffer overflow + +When the buffer overflows, the stream fails with an `IllegalStateException`. + +**Important**: The buffer only holds items that were already requested from upstream before pausing. +When paused, no new requests are issued to upstream, so the buffer size is bounded by the outstanding demand at the time of pausing. + +#### DROP strategy + +Already-requested items are dropped while paused: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "drop-strategy")} +``` + +Items that arrive while paused are discarded, and when resumed, the stream continues requesting fresh items. + +#### IGNORE strategy + +Already-requested items continue to flow downstream while paused. +This strategy doesn't use any buffers. +It only pauses demand from being issued to upstream, but does not pause the flow of already requested items. + +### Buffer management + +When using the BUFFER strategy, you can inspect and manage the buffer: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "buffer-management")} +``` + +The `DemandPauser` provides: + +- `bufferSize()`: Returns the current number of buffered items +- `clearBuffer()`: Clears the buffer (only works while paused), returns `true` if successful + diff --git a/documentation/docs-roq/content/3.1.0/guides/converters.md b/documentation/docs-roq/content/3.1.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/3.1.0/guides/custom-operators.md b/documentation/docs-roq/content/3.1.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/3.1.0/guides/delaying-events.md b/documentation/docs-roq/content/3.1.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/3.1.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/3.1.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/3.1.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/3.1.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/guides/emission-threads.md b/documentation/docs-roq/content/3.1.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/3.1.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/3.1.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/guides/filtering-items.md b/documentation/docs-roq/content/3.1.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/3.1.0/guides/framework-integration.md b/documentation/docs-roq/content/3.1.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/3.1.0/guides/handling-null.md b/documentation/docs-roq/content/3.1.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/guides/handling-timeouts.md b/documentation/docs-roq/content/3.1.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/guides/hot-streams.md b/documentation/docs-roq/content/3.1.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/3.1.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/3.1.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/3.1.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/3.1.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/guides/joining-unis.md b/documentation/docs-roq/content/3.1.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/3.1.0/guides/kotlin.md b/documentation/docs-roq/content/3.1.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/3.1.0/guides/logging.md b/documentation/docs-roq/content/3.1.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/3.1.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/3.1.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/3.1.0/guides/multi-split.md b/documentation/docs-roq/content/3.1.0/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/3.1.0/guides/pagination.md b/documentation/docs-roq/content/3.1.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/guides/polling.md b/documentation/docs-roq/content/3.1.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/3.1.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/guides/replaying-multis.md b/documentation/docs-roq/content/3.1.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/guides/rx.md b/documentation/docs-roq/content/3.1.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/3.1.0/guides/shortcut-methods.md b/documentation/docs-roq/content/3.1.0/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/3.1.0/guides/spies.md b/documentation/docs-roq/content/3.1.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/guides/take-skip-items.md b/documentation/docs-roq/content/3.1.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/3.1.0/guides/testing.md b/documentation/docs-roq/content/3.1.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/3.1.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/3.1.0/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/3.1.0/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/3.1.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/3.1.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/3.1.0/reference/publications.md b/documentation/docs-roq/content/3.1.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/reference/uni-and-multi.md b/documentation/docs-roq/content/3.1.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/3.1.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/3.1.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/3.1.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/3.1.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/3.1.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/3.1.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/3.1.0/tags-index.md b/documentation/docs-roq/content/3.1.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/3.1.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/3.1.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/3.1.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/3.1.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/3.1.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/3.1.0/tutorials/handling-failures.md b/documentation/docs-roq/content/3.1.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/3.1.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/3.1.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/3.1.0/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/3.1.0/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/3.1.0/tutorials/observing-events.md b/documentation/docs-roq/content/3.1.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/3.1.0/tutorials/retrying.md b/documentation/docs-roq/content/3.1.0/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/3.1.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/3.1.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d11c37e37 --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,132 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` diff --git a/documentation/docs-roq/content/3.1.0/tutorials/transforming-items.md b/documentation/docs-roq/content/3.1.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/3.1.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/3.1.1/guides/branching.md b/documentation/docs-roq/content/3.1.1/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/3.1.1/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/3.1.1/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/3.1.1/guides/collecting-items.md b/documentation/docs-roq/content/3.1.1/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/3.1.1/guides/combining-items.md b/documentation/docs-roq/content/3.1.1/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/3.1.1/guides/completion-stage.md b/documentation/docs-roq/content/3.1.1/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/3.1.1/guides/context-passing.md b/documentation/docs-roq/content/3.1.1/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/3.1.1/guides/controlling-demand.md b/documentation/docs-roq/content/3.1.1/guides/controlling-demand.md new file mode 100644 index 000000000..ed21844c3 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/controlling-demand.md @@ -0,0 +1,155 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. + +## Pausing the demand + +The `Multi.pauseDemand()` operator provides fine-grained control over demand propagation in reactive streams. +Unlike cancellation, which terminates the subscription, pausing allows to suspend demand without unsubscribing from the upstream. +This is useful for implementing flow control patterns where item flow needs to be paused based on external conditions. + +### Basic pausing and resuming + +The `pauseDemand()` operator works with a `DemandPauser` handle that allows to control the stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "basic")} +``` + +The `DemandPauser` provides methods to: + +- `pause()`: Stop propagating demand to upstream +- `resume()`: Resume demand propagation and deliver buffered items +- `isPaused()`: Check the current pause state + +Note that a few items may still arrive after pausing due to in-flight requests that were already issued to upstream. + +### Starting in a paused state + +You can create a stream that starts paused and only begins flowing when explicitly resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "initially-paused")} +``` + +This is useful when you want to prepare a stream but delay its execution until certain conditions are met. + +### Late subscription + +By default, the upstream subscription happens immediately even when starting paused. +The `lateSubscription()` option delays the upstream subscription until the stream is resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "late-subscription")} +``` + +### Buffer strategies + +When a stream is paused, the operator stops requesting new items from upstream. +However, items that were already requested (due to downstream demand) may still arrive. +Buffer strategies control what happens to these in-flight items. + +The `pauseDemand()` operator supports three buffer strategies: `BUFFER` (default), `DROP`, and `IGNORE`. +Configuring any other strategy will throw an `IllegalArgumentException`. + +#### BUFFER strategy (default) + +Already-requested items are buffered while paused and delivered when resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "buffer-strategy")} +``` + +You can configure the buffer size: + +- `bufferUnconditionally()`: Unbounded buffer +- `bufferSize(n)`: Buffer up to `n` items, then fail with buffer overflow + +When the buffer overflows, the stream fails with an `IllegalStateException`. + +**Important**: The buffer only holds items that were already requested from upstream before pausing. +When paused, no new requests are issued to upstream, so the buffer size is bounded by the outstanding demand at the time of pausing. + +#### DROP strategy + +Already-requested items are dropped while paused: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "drop-strategy")} +``` + +Items that arrive while paused are discarded, and when resumed, the stream continues requesting fresh items. + +#### IGNORE strategy + +Already-requested items continue to flow downstream while paused. +This strategy doesn't use any buffers. +It only pauses demand from being issued to upstream, but does not pause the flow of already requested items. + +### Buffer management + +When using the BUFFER strategy, you can inspect and manage the buffer: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "buffer-management")} +``` + +The `DemandPauser` provides: + +- `bufferSize()`: Returns the current number of buffered items +- `clearBuffer()`: Clears the buffer (only works while paused), returns `true` if successful + diff --git a/documentation/docs-roq/content/3.1.1/guides/converters.md b/documentation/docs-roq/content/3.1.1/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/3.1.1/guides/custom-operators.md b/documentation/docs-roq/content/3.1.1/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/3.1.1/guides/delaying-events.md b/documentation/docs-roq/content/3.1.1/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/3.1.1/guides/dropped-exceptions.md b/documentation/docs-roq/content/3.1.1/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/3.1.1/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/3.1.1/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/guides/emission-threads.md b/documentation/docs-roq/content/3.1.1/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/3.1.1/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/3.1.1/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/guides/filtering-items.md b/documentation/docs-roq/content/3.1.1/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/3.1.1/guides/framework-integration.md b/documentation/docs-roq/content/3.1.1/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/3.1.1/guides/grouping-items.md b/documentation/docs-roq/content/3.1.1/guides/grouping-items.md new file mode 100644 index 000000000..415ef2891 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/grouping-items.md @@ -0,0 +1,145 @@ +--- +title: "Grouping items from Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Grouping items from Multi + +Mutiny provides several operators to group items from a `Multi` stream. +You can group items by a key function (similar to SQL's `GROUP BY`), split items into fixed-size chunks, or create time-based windows. + +The grouping operators are available from the `group()` method on `Multi`. + +## Grouping into Lists + +The `group().intoLists()` operator allows you to collect items into lists based on size or time. + +### Fixed-size lists + +Use `group().intoLists().of(size)` to create fixed-size lists from the stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupIntoLists")} +``` + +The last list may contain fewer items if the stream doesn't divide evenly. + +### Time-based lists + +You can create time-based lists using `group().intoLists().every(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "timeBasedListGrouping")} +``` + +### Size and time-based lists + +You can combine both size and time constraints using `group().intoLists().of(size, Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "sizeAndTimeBasedListGrouping")} +``` + +This will emit a list when either the size limit is reached or the duration expires, whichever comes first. + +## Grouping into Multi Streams + +The `group().intoMultis()` operator allows you to create separate `Multi` streams from your data. Unlike `intoLists()` which materializes all items into memory, `intoMultis()` keeps items as streams, which is better for: + +- Applying stream transformations to each group +- Processing large groups without loading everything into memory +- Composing with other reactive operators + +### Fixed-size Multi streams + +Use `group().intoMultis().of(size)` to create `Multi` streams of a fixed size: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupIntoMultis")} +``` + +### Time-based Multi streams + +You can create time-based windows using `group().intoMultis().every(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "timeBasedGrouping")} +``` + +## Grouping by a key function + +The `group().by()` operator groups items based on a key function, emitting a `Multi>` where each `GroupedMulti` represents a group of items sharing the same key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByKey")} +``` + +Each `GroupedMulti` has a `key()` method that returns the key for that group. +Items are distributed to groups based on the key function result. + +### Using both key and value mappers + +You can transform items while grouping them by providing both a key mapper and a value mapper: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByKeyAndValue")} +``` + + +## Processing groups with merge vs concatenate + +When processing groups, you need to decide how to combine the results back into a single stream. Similar to [transforming items asynchronously](../tutorials/transforming-items-asynchronously.md), you can use either **merge** or **concatenate**: + +### Using merge + +With merge, groups are processed concurrently - items from different groups can interleave in the output stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByWithMerge")} +``` + +> [!WARNING] +> **Upstream request starvation with merge** +> +> When using `.merge(concurrency)` or similar merge operations after `group().by()`, +> **the concurrency parameter must be greater than or equal to the number of groups that are created but not terminated**. +> +> If you create more groups than the concurrency limit allows, some groups cannot make progress while waiting for others to complete. +> This leads to a **request starvation** where the upstream won't receive requests for emitting new items. + +#### Example causing request starvation + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByDeadlock")} +``` + +In this example, 10 groups are created but only 2 can be processed concurrently. +Groups 3-10 cannot make progress because the downstream subscriber is busy with groups 1-2. +Meanwhile, groups 1-2 may not complete because they're waiting for backpressure signals from the full pipeline. +The problem is even more exacerbated with infinite streams and infinite groups. + +#### How to avoid request starvation + +1. **Set concurrency >= number of groups**: If you know the maximum number of groups in advance, set the concurrency parameter to at least that number using `.merge(n)` +2. **Use unbounded concurrency**: Call `.merge(Integer.MAX_VALUE)` to allow unlimited number of concurrent groups +3. **Use concatenate instead**: Process groups sequentially (see below) + +### Using concatenate + +With concatenate, groups are processed sequentially - each group must fully terminate before the next group can start processing: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByWithConcatenate")} +``` + +## Choosing between group().by() and split() + +Mutiny provides both `group().by()` and `split()` operators. Here's when to use each: + +- **Use `group().by()`** when you don't know the keys in advance and the number of groups is dynamic. +- **Use `split()`** when you know all possible keys upfront (defined by an enum) and you want individual `Multi` instances for each split. + +See the [splitting guide](multi-split.md) for more details on `split()`. diff --git a/documentation/docs-roq/content/3.1.1/guides/handling-null.md b/documentation/docs-roq/content/3.1.1/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/guides/handling-timeouts.md b/documentation/docs-roq/content/3.1.1/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/guides/hot-streams.md b/documentation/docs-roq/content/3.1.1/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/3.1.1/guides/imperative-to-reactive.md b/documentation/docs-roq/content/3.1.1/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/3.1.1/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/3.1.1/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/guides/joining-unis.md b/documentation/docs-roq/content/3.1.1/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/3.1.1/guides/kotlin.md b/documentation/docs-roq/content/3.1.1/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/3.1.1/guides/logging.md b/documentation/docs-roq/content/3.1.1/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/3.1.1/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/3.1.1/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/3.1.1/guides/multi-split.md b/documentation/docs-roq/content/3.1.1/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/3.1.1/guides/pagination.md b/documentation/docs-roq/content/3.1.1/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/guides/polling.md b/documentation/docs-roq/content/3.1.1/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/guides/reactive-to-imperative.md b/documentation/docs-roq/content/3.1.1/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/guides/replaying-multis.md b/documentation/docs-roq/content/3.1.1/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/guides/rx.md b/documentation/docs-roq/content/3.1.1/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/3.1.1/guides/shortcut-methods.md b/documentation/docs-roq/content/3.1.1/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/3.1.1/guides/spies.md b/documentation/docs-roq/content/3.1.1/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/guides/take-skip-items.md b/documentation/docs-roq/content/3.1.1/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/3.1.1/guides/testing.md b/documentation/docs-roq/content/3.1.1/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/guides/unchecked-exceptions.md b/documentation/docs-roq/content/3.1.1/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/3.1.1/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/3.1.1/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/3.1.1/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/3.1.1/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/3.1.1/reference/publications.md b/documentation/docs-roq/content/3.1.1/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/reference/uni-and-multi.md b/documentation/docs-roq/content/3.1.1/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/3.1.1/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/3.1.1/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/3.1.1/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/3.1.1/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/3.1.1/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/3.1.1/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/3.1.1/tags-index.md b/documentation/docs-roq/content/3.1.1/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/3.1.1/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/3.1.1/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/3.1.1/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/3.1.1/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/3.1.1/tutorials/getting-mutiny.md b/documentation/docs-roq/content/3.1.1/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/3.1.1/tutorials/handling-failures.md b/documentation/docs-roq/content/3.1.1/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/3.1.1/tutorials/hello-mutiny.md b/documentation/docs-roq/content/3.1.1/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/3.1.1/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/3.1.1/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/3.1.1/tutorials/observing-events.md b/documentation/docs-roq/content/3.1.1/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/3.1.1/tutorials/retrying.md b/documentation/docs-roq/content/3.1.1/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/3.1.1/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/3.1.1/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d686f2457 --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,152 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +### Controlling concurrency with merge + +The `merge` method accepts an optional `concurrency` parameter that limits how many inner streams can be subscribed to concurrently: + +```java +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concurrency")} +``` + +When not provided, the default concurrency is configured using `Infrastructure.getBufferSizeS()`. + +> [!WARNING] +> **Merge concurrency** +> +> When using merge with limited concurrency, be aware of potential backpressure issues. +> **Setting concurrency too low** can cause upstream request starvation if the number of subscribed but not emitting inner streams surpasses the level of concurrency. +> **Unbounded concurrency** eliminates the request starvation issue by removing the limit on the number of subscribed inner streams to merge. + + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` + +Like after `transformToUni`, the `merge` method after `transformToMulti` also accepts an optional concurrency parameter with the same considerations regarding backpressure and request starvation when used with infinite streams. diff --git a/documentation/docs-roq/content/3.1.1/tutorials/transforming-items.md b/documentation/docs-roq/content/3.1.1/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/3.1.1/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/3.2.0/guides/branching.md b/documentation/docs-roq/content/3.2.0/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/3.2.0/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/3.2.0/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/3.2.0/guides/collecting-items.md b/documentation/docs-roq/content/3.2.0/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/3.2.0/guides/combining-items.md b/documentation/docs-roq/content/3.2.0/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/3.2.0/guides/completion-stage.md b/documentation/docs-roq/content/3.2.0/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/3.2.0/guides/context-passing.md b/documentation/docs-roq/content/3.2.0/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/3.2.0/guides/controlling-demand.md b/documentation/docs-roq/content/3.2.0/guides/controlling-demand.md new file mode 100644 index 000000000..ed21844c3 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/controlling-demand.md @@ -0,0 +1,155 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. + +## Pausing the demand + +The `Multi.pauseDemand()` operator provides fine-grained control over demand propagation in reactive streams. +Unlike cancellation, which terminates the subscription, pausing allows to suspend demand without unsubscribing from the upstream. +This is useful for implementing flow control patterns where item flow needs to be paused based on external conditions. + +### Basic pausing and resuming + +The `pauseDemand()` operator works with a `DemandPauser` handle that allows to control the stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "basic")} +``` + +The `DemandPauser` provides methods to: + +- `pause()`: Stop propagating demand to upstream +- `resume()`: Resume demand propagation and deliver buffered items +- `isPaused()`: Check the current pause state + +Note that a few items may still arrive after pausing due to in-flight requests that were already issued to upstream. + +### Starting in a paused state + +You can create a stream that starts paused and only begins flowing when explicitly resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "initially-paused")} +``` + +This is useful when you want to prepare a stream but delay its execution until certain conditions are met. + +### Late subscription + +By default, the upstream subscription happens immediately even when starting paused. +The `lateSubscription()` option delays the upstream subscription until the stream is resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "late-subscription")} +``` + +### Buffer strategies + +When a stream is paused, the operator stops requesting new items from upstream. +However, items that were already requested (due to downstream demand) may still arrive. +Buffer strategies control what happens to these in-flight items. + +The `pauseDemand()` operator supports three buffer strategies: `BUFFER` (default), `DROP`, and `IGNORE`. +Configuring any other strategy will throw an `IllegalArgumentException`. + +#### BUFFER strategy (default) + +Already-requested items are buffered while paused and delivered when resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "buffer-strategy")} +``` + +You can configure the buffer size: + +- `bufferUnconditionally()`: Unbounded buffer +- `bufferSize(n)`: Buffer up to `n` items, then fail with buffer overflow + +When the buffer overflows, the stream fails with an `IllegalStateException`. + +**Important**: The buffer only holds items that were already requested from upstream before pausing. +When paused, no new requests are issued to upstream, so the buffer size is bounded by the outstanding demand at the time of pausing. + +#### DROP strategy + +Already-requested items are dropped while paused: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "drop-strategy")} +``` + +Items that arrive while paused are discarded, and when resumed, the stream continues requesting fresh items. + +#### IGNORE strategy + +Already-requested items continue to flow downstream while paused. +This strategy doesn't use any buffers. +It only pauses demand from being issued to upstream, but does not pause the flow of already requested items. + +### Buffer management + +When using the BUFFER strategy, you can inspect and manage the buffer: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "buffer-management")} +``` + +The `DemandPauser` provides: + +- `bufferSize()`: Returns the current number of buffered items +- `clearBuffer()`: Clears the buffer (only works while paused), returns `true` if successful + diff --git a/documentation/docs-roq/content/3.2.0/guides/converters.md b/documentation/docs-roq/content/3.2.0/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/3.2.0/guides/custom-operators.md b/documentation/docs-roq/content/3.2.0/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/3.2.0/guides/delaying-events.md b/documentation/docs-roq/content/3.2.0/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/3.2.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/3.2.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/3.2.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/3.2.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/guides/emission-threads.md b/documentation/docs-roq/content/3.2.0/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/3.2.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/3.2.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/guides/filtering-items.md b/documentation/docs-roq/content/3.2.0/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/3.2.0/guides/framework-integration.md b/documentation/docs-roq/content/3.2.0/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/3.2.0/guides/grouping-items.md b/documentation/docs-roq/content/3.2.0/guides/grouping-items.md new file mode 100644 index 000000000..415ef2891 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/grouping-items.md @@ -0,0 +1,145 @@ +--- +title: "Grouping items from Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Grouping items from Multi + +Mutiny provides several operators to group items from a `Multi` stream. +You can group items by a key function (similar to SQL's `GROUP BY`), split items into fixed-size chunks, or create time-based windows. + +The grouping operators are available from the `group()` method on `Multi`. + +## Grouping into Lists + +The `group().intoLists()` operator allows you to collect items into lists based on size or time. + +### Fixed-size lists + +Use `group().intoLists().of(size)` to create fixed-size lists from the stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupIntoLists")} +``` + +The last list may contain fewer items if the stream doesn't divide evenly. + +### Time-based lists + +You can create time-based lists using `group().intoLists().every(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "timeBasedListGrouping")} +``` + +### Size and time-based lists + +You can combine both size and time constraints using `group().intoLists().of(size, Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "sizeAndTimeBasedListGrouping")} +``` + +This will emit a list when either the size limit is reached or the duration expires, whichever comes first. + +## Grouping into Multi Streams + +The `group().intoMultis()` operator allows you to create separate `Multi` streams from your data. Unlike `intoLists()` which materializes all items into memory, `intoMultis()` keeps items as streams, which is better for: + +- Applying stream transformations to each group +- Processing large groups without loading everything into memory +- Composing with other reactive operators + +### Fixed-size Multi streams + +Use `group().intoMultis().of(size)` to create `Multi` streams of a fixed size: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupIntoMultis")} +``` + +### Time-based Multi streams + +You can create time-based windows using `group().intoMultis().every(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "timeBasedGrouping")} +``` + +## Grouping by a key function + +The `group().by()` operator groups items based on a key function, emitting a `Multi>` where each `GroupedMulti` represents a group of items sharing the same key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByKey")} +``` + +Each `GroupedMulti` has a `key()` method that returns the key for that group. +Items are distributed to groups based on the key function result. + +### Using both key and value mappers + +You can transform items while grouping them by providing both a key mapper and a value mapper: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByKeyAndValue")} +``` + + +## Processing groups with merge vs concatenate + +When processing groups, you need to decide how to combine the results back into a single stream. Similar to [transforming items asynchronously](../tutorials/transforming-items-asynchronously.md), you can use either **merge** or **concatenate**: + +### Using merge + +With merge, groups are processed concurrently - items from different groups can interleave in the output stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByWithMerge")} +``` + +> [!WARNING] +> **Upstream request starvation with merge** +> +> When using `.merge(concurrency)` or similar merge operations after `group().by()`, +> **the concurrency parameter must be greater than or equal to the number of groups that are created but not terminated**. +> +> If you create more groups than the concurrency limit allows, some groups cannot make progress while waiting for others to complete. +> This leads to a **request starvation** where the upstream won't receive requests for emitting new items. + +#### Example causing request starvation + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByDeadlock")} +``` + +In this example, 10 groups are created but only 2 can be processed concurrently. +Groups 3-10 cannot make progress because the downstream subscriber is busy with groups 1-2. +Meanwhile, groups 1-2 may not complete because they're waiting for backpressure signals from the full pipeline. +The problem is even more exacerbated with infinite streams and infinite groups. + +#### How to avoid request starvation + +1. **Set concurrency >= number of groups**: If you know the maximum number of groups in advance, set the concurrency parameter to at least that number using `.merge(n)` +2. **Use unbounded concurrency**: Call `.merge(Integer.MAX_VALUE)` to allow unlimited number of concurrent groups +3. **Use concatenate instead**: Process groups sequentially (see below) + +### Using concatenate + +With concatenate, groups are processed sequentially - each group must fully terminate before the next group can start processing: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByWithConcatenate")} +``` + +## Choosing between group().by() and split() + +Mutiny provides both `group().by()` and `split()` operators. Here's when to use each: + +- **Use `group().by()`** when you don't know the keys in advance and the number of groups is dynamic. +- **Use `split()`** when you know all possible keys upfront (defined by an enum) and you want individual `Multi` instances for each split. + +See the [splitting guide](multi-split.md) for more details on `split()`. diff --git a/documentation/docs-roq/content/3.2.0/guides/handling-null.md b/documentation/docs-roq/content/3.2.0/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/guides/handling-timeouts.md b/documentation/docs-roq/content/3.2.0/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/guides/hot-streams.md b/documentation/docs-roq/content/3.2.0/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/3.2.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/3.2.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/3.2.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/3.2.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/guides/joining-unis.md b/documentation/docs-roq/content/3.2.0/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/3.2.0/guides/kotlin.md b/documentation/docs-roq/content/3.2.0/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/3.2.0/guides/logging.md b/documentation/docs-roq/content/3.2.0/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/3.2.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/3.2.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/3.2.0/guides/multi-split.md b/documentation/docs-roq/content/3.2.0/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/3.2.0/guides/pagination.md b/documentation/docs-roq/content/3.2.0/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/guides/polling.md b/documentation/docs-roq/content/3.2.0/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/3.2.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/guides/replaying-multis.md b/documentation/docs-roq/content/3.2.0/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/guides/rx.md b/documentation/docs-roq/content/3.2.0/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/3.2.0/guides/shortcut-methods.md b/documentation/docs-roq/content/3.2.0/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/3.2.0/guides/spies.md b/documentation/docs-roq/content/3.2.0/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/guides/take-skip-items.md b/documentation/docs-roq/content/3.2.0/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/3.2.0/guides/testing.md b/documentation/docs-roq/content/3.2.0/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/3.2.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/3.2.0/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/3.2.0/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/3.2.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/3.2.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/3.2.0/reference/publications.md b/documentation/docs-roq/content/3.2.0/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/reference/uni-and-multi.md b/documentation/docs-roq/content/3.2.0/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/3.2.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/3.2.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/3.2.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/3.2.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/3.2.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/3.2.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/3.2.0/tags-index.md b/documentation/docs-roq/content/3.2.0/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/3.2.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/3.2.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/3.2.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/3.2.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/3.2.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/3.2.0/tutorials/handling-failures.md b/documentation/docs-roq/content/3.2.0/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/3.2.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/3.2.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/3.2.0/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/3.2.0/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/3.2.0/tutorials/observing-events.md b/documentation/docs-roq/content/3.2.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/3.2.0/tutorials/retrying.md b/documentation/docs-roq/content/3.2.0/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/3.2.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/3.2.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d686f2457 --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,152 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +### Controlling concurrency with merge + +The `merge` method accepts an optional `concurrency` parameter that limits how many inner streams can be subscribed to concurrently: + +```java +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concurrency")} +``` + +When not provided, the default concurrency is configured using `Infrastructure.getBufferSizeS()`. + +> [!WARNING] +> **Merge concurrency** +> +> When using merge with limited concurrency, be aware of potential backpressure issues. +> **Setting concurrency too low** can cause upstream request starvation if the number of subscribed but not emitting inner streams surpasses the level of concurrency. +> **Unbounded concurrency** eliminates the request starvation issue by removing the limit on the number of subscribed inner streams to merge. + + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` + +Like after `transformToUni`, the `merge` method after `transformToMulti` also accepts an optional concurrency parameter with the same considerations regarding backpressure and request starvation when used with infinite streams. diff --git a/documentation/docs-roq/content/3.2.0/tutorials/transforming-items.md b/documentation/docs-roq/content/3.2.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/3.2.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/3.2.1/guides/branching.md b/documentation/docs-roq/content/3.2.1/guides/branching.md new file mode 100644 index 000000000..8b5961b5f --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/branching.md @@ -0,0 +1,55 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/3.2.1/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/3.2.1/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..4612a8d27 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,204 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource { + + @GET + @Path("ticks/{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) { + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) { + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> { + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> { + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/3.2.1/guides/collecting-items.md b/documentation/docs-roq/content/3.2.1/guides/collecting-items.md new file mode 100644 index 000000000..4a1b2800c --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/collecting-items.md @@ -0,0 +1,98 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/3.2.1/guides/combining-items.md b/documentation/docs-roq/content/3.2.1/guides/combining-items.md new file mode 100644 index 000000000..5fb3c6cdd --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/combining-items.md @@ -0,0 +1,159 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/3.2.1/guides/completion-stage.md b/documentation/docs-roq/content/3.2.1/guides/completion-stage.md new file mode 100644 index 000000000..31856400a --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/completion-stage.md @@ -0,0 +1,84 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/3.2.1/guides/context-passing.md b/documentation/docs-roq/content/3.2.1/guides/context-passing.md new file mode 100644 index 000000000..9b20f0780 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/context-passing.md @@ -0,0 +1,84 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/3.2.1/guides/controlling-demand.md b/documentation/docs-roq/content/3.2.1/guides/controlling-demand.md new file mode 100644 index 000000000..ed21844c3 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/controlling-demand.md @@ -0,0 +1,155 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. + +## Pausing the demand + +The `Multi.pauseDemand()` operator provides fine-grained control over demand propagation in reactive streams. +Unlike cancellation, which terminates the subscription, pausing allows to suspend demand without unsubscribing from the upstream. +This is useful for implementing flow control patterns where item flow needs to be paused based on external conditions. + +### Basic pausing and resuming + +The `pauseDemand()` operator works with a `DemandPauser` handle that allows to control the stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "basic")} +``` + +The `DemandPauser` provides methods to: + +- `pause()`: Stop propagating demand to upstream +- `resume()`: Resume demand propagation and deliver buffered items +- `isPaused()`: Check the current pause state + +Note that a few items may still arrive after pausing due to in-flight requests that were already issued to upstream. + +### Starting in a paused state + +You can create a stream that starts paused and only begins flowing when explicitly resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "initially-paused")} +``` + +This is useful when you want to prepare a stream but delay its execution until certain conditions are met. + +### Late subscription + +By default, the upstream subscription happens immediately even when starting paused. +The `lateSubscription()` option delays the upstream subscription until the stream is resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "late-subscription")} +``` + +### Buffer strategies + +When a stream is paused, the operator stops requesting new items from upstream. +However, items that were already requested (due to downstream demand) may still arrive. +Buffer strategies control what happens to these in-flight items. + +The `pauseDemand()` operator supports three buffer strategies: `BUFFER` (default), `DROP`, and `IGNORE`. +Configuring any other strategy will throw an `IllegalArgumentException`. + +#### BUFFER strategy (default) + +Already-requested items are buffered while paused and delivered when resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "buffer-strategy")} +``` + +You can configure the buffer size: + +- `bufferUnconditionally()`: Unbounded buffer +- `bufferSize(n)`: Buffer up to `n` items, then fail with buffer overflow + +When the buffer overflows, the stream fails with an `IllegalStateException`. + +**Important**: The buffer only holds items that were already requested from upstream before pausing. +When paused, no new requests are issued to upstream, so the buffer size is bounded by the outstanding demand at the time of pausing. + +#### DROP strategy + +Already-requested items are dropped while paused: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "drop-strategy")} +``` + +Items that arrive while paused are discarded, and when resumed, the stream continues requesting fresh items. + +#### IGNORE strategy + +Already-requested items continue to flow downstream while paused. +This strategy doesn't use any buffers. +It only pauses demand from being issued to upstream, but does not pause the flow of already requested items. + +### Buffer management + +When using the BUFFER strategy, you can inspect and manage the buffer: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "buffer-management")} +``` + +The `DemandPauser` provides: + +- `bufferSize()`: Returns the current number of buffered items +- `clearBuffer()`: Clears the buffer (only works while paused), returns `true` if successful + diff --git a/documentation/docs-roq/content/3.2.1/guides/converters.md b/documentation/docs-roq/content/3.2.1/guides/converters.md new file mode 100644 index 000000000..c9f25fddf --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/converters.md @@ -0,0 +1,219 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!NOTE] +> +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!NOTE] +> +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/3.2.1/guides/custom-operators.md b/documentation/docs-roq/content/3.2.1/guides/custom-operators.md new file mode 100644 index 000000000..8043e7e3a --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/custom-operators.md @@ -0,0 +1,31 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/3.2.1/guides/delaying-events.md b/documentation/docs-roq/content/3.2.1/guides/delaying-events.md new file mode 100644 index 000000000..0135cc440 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/delaying-events.md @@ -0,0 +1,65 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/3.2.1/guides/dropped-exceptions.md b/documentation/docs-roq/content/3.2.1/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/3.2.1/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/3.2.1/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..4f8771587 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,54 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: +- guide +- beginner +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/guides/emission-threads.md b/documentation/docs-roq/content/3.2.1/guides/emission-threads.md new file mode 100644 index 000000000..97cd513e6 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/emission-threads.md @@ -0,0 +1,33 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: +- guide +- intermediate +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/3.2.1/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/3.2.1/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..0b4c3c438 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,67 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: +- guide +- intermediate +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/guides/filtering-items.md b/documentation/docs-roq/content/3.2.1/guides/filtering-items.md new file mode 100644 index 000000000..2ef9ebe91 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/filtering-items.md @@ -0,0 +1,36 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/3.2.1/guides/framework-integration.md b/documentation/docs-roq/content/3.2.1/guides/framework-integration.md new file mode 100644 index 000000000..631a62f7c --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/framework-integration.md @@ -0,0 +1,32 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: +- guide +- advanced +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/3.2.1/guides/grouping-items.md b/documentation/docs-roq/content/3.2.1/guides/grouping-items.md new file mode 100644 index 000000000..415ef2891 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/grouping-items.md @@ -0,0 +1,145 @@ +--- +title: "Grouping items from Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Grouping items from Multi + +Mutiny provides several operators to group items from a `Multi` stream. +You can group items by a key function (similar to SQL's `GROUP BY`), split items into fixed-size chunks, or create time-based windows. + +The grouping operators are available from the `group()` method on `Multi`. + +## Grouping into Lists + +The `group().intoLists()` operator allows you to collect items into lists based on size or time. + +### Fixed-size lists + +Use `group().intoLists().of(size)` to create fixed-size lists from the stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupIntoLists")} +``` + +The last list may contain fewer items if the stream doesn't divide evenly. + +### Time-based lists + +You can create time-based lists using `group().intoLists().every(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "timeBasedListGrouping")} +``` + +### Size and time-based lists + +You can combine both size and time constraints using `group().intoLists().of(size, Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "sizeAndTimeBasedListGrouping")} +``` + +This will emit a list when either the size limit is reached or the duration expires, whichever comes first. + +## Grouping into Multi Streams + +The `group().intoMultis()` operator allows you to create separate `Multi` streams from your data. Unlike `intoLists()` which materializes all items into memory, `intoMultis()` keeps items as streams, which is better for: + +- Applying stream transformations to each group +- Processing large groups without loading everything into memory +- Composing with other reactive operators + +### Fixed-size Multi streams + +Use `group().intoMultis().of(size)` to create `Multi` streams of a fixed size: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupIntoMultis")} +``` + +### Time-based Multi streams + +You can create time-based windows using `group().intoMultis().every(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "timeBasedGrouping")} +``` + +## Grouping by a key function + +The `group().by()` operator groups items based on a key function, emitting a `Multi>` where each `GroupedMulti` represents a group of items sharing the same key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByKey")} +``` + +Each `GroupedMulti` has a `key()` method that returns the key for that group. +Items are distributed to groups based on the key function result. + +### Using both key and value mappers + +You can transform items while grouping them by providing both a key mapper and a value mapper: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByKeyAndValue")} +``` + + +## Processing groups with merge vs concatenate + +When processing groups, you need to decide how to combine the results back into a single stream. Similar to [transforming items asynchronously](../tutorials/transforming-items-asynchronously.md), you can use either **merge** or **concatenate**: + +### Using merge + +With merge, groups are processed concurrently - items from different groups can interleave in the output stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByWithMerge")} +``` + +> [!WARNING] +> **Upstream request starvation with merge** +> +> When using `.merge(concurrency)` or similar merge operations after `group().by()`, +> **the concurrency parameter must be greater than or equal to the number of groups that are created but not terminated**. +> +> If you create more groups than the concurrency limit allows, some groups cannot make progress while waiting for others to complete. +> This leads to a **request starvation** where the upstream won't receive requests for emitting new items. + +#### Example causing request starvation + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByDeadlock")} +``` + +In this example, 10 groups are created but only 2 can be processed concurrently. +Groups 3-10 cannot make progress because the downstream subscriber is busy with groups 1-2. +Meanwhile, groups 1-2 may not complete because they're waiting for backpressure signals from the full pipeline. +The problem is even more exacerbated with infinite streams and infinite groups. + +#### How to avoid request starvation + +1. **Set concurrency >= number of groups**: If you know the maximum number of groups in advance, set the concurrency parameter to at least that number using `.merge(n)` +2. **Use unbounded concurrency**: Call `.merge(Integer.MAX_VALUE)` to allow unlimited number of concurrent groups +3. **Use concatenate instead**: Process groups sequentially (see below) + +### Using concatenate + +With concatenate, groups are processed sequentially - each group must fully terminate before the next group can start processing: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByWithConcatenate")} +``` + +## Choosing between group().by() and split() + +Mutiny provides both `group().by()` and `split()` operators. Here's when to use each: + +- **Use `group().by()`** when you don't know the keys in advance and the number of groups is dynamic. +- **Use `split()`** when you know all possible keys upfront (defined by an enum) and you want individual `Multi` instances for each split. + +See the [splitting guide](multi-split.md) for more details on `split()`. diff --git a/documentation/docs-roq/content/3.2.1/guides/handling-null.md b/documentation/docs-roq/content/3.2.1/guides/handling-null.md new file mode 100644 index 000000000..1f66279b0 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/handling-null.md @@ -0,0 +1,37 @@ +--- +title: "How to handle null?" +layout: page +tags: +- guide +- beginner +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> +> While supported, emitting `null` should be avoided except for `Uni`. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/guides/handling-timeouts.md b/documentation/docs-roq/content/3.2.1/guides/handling-timeouts.md new file mode 100644 index 000000000..e39178353 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/handling-timeouts.md @@ -0,0 +1,52 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: +- guide +- intermediate +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/guides/hot-streams.md b/documentation/docs-roq/content/3.2.1/guides/hot-streams.md new file mode 100644 index 000000000..c37457355 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/hot-streams.md @@ -0,0 +1,30 @@ +--- +title: "Hot streams" +layout: page +tags: +- guide +- advanced +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/3.2.1/guides/imperative-to-reactive.md b/documentation/docs-roq/content/3.2.1/guides/imperative-to-reactive.md new file mode 100644 index 000000000..371dcedd9 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: +- guide +- advanced +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + diff --git a/documentation/docs-roq/content/3.2.1/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/3.2.1/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..17b9bcfd7 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,24 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: +- guide +- advanced +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/guides/joining-unis.md b/documentation/docs-roq/content/3.2.1/guides/joining-unis.md new file mode 100644 index 000000000..6bc6f450a --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/joining-unis.md @@ -0,0 +1,67 @@ +--- +title: "Joining several unis" +layout: page +tags: +- guide +- intermediate +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/3.2.1/guides/kotlin.md b/documentation/docs-roq/content/3.2.1/guides/kotlin.md new file mode 100644 index 000000000..0dbc76306 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/kotlin.md @@ -0,0 +1,109 @@ +--- +title: "Kotlin integration" +layout: page +tags: +- guide +- intermediate +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` diff --git a/documentation/docs-roq/content/3.2.1/guides/logging.md b/documentation/docs-roq/content/3.2.1/guides/logging.md new file mode 100644 index 000000000..636995db0 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/logging.md @@ -0,0 +1,65 @@ +--- +title: "Logging events" +layout: page +tags: +- guide +- beginner +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. diff --git a/documentation/docs-roq/content/3.2.1/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/3.2.1/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..c925b5d26 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/merging-and-concatenating-streams.md @@ -0,0 +1,127 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: +- guide +- intermediate +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + diff --git a/documentation/docs-roq/content/3.2.1/guides/multi-split.md b/documentation/docs-roq/content/3.2.1/guides/multi-split.md new file mode 100644 index 000000000..e3bcf4756 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/multi-split.md @@ -0,0 +1,54 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: +- guide +- intermediate +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/3.2.1/guides/pagination.md b/documentation/docs-roq/content/3.2.1/guides/pagination.md new file mode 100644 index 000000000..556b05e94 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/pagination.md @@ -0,0 +1,62 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: +- guide +- intermediate +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/guides/polling.md b/documentation/docs-roq/content/3.2.1/guides/polling.md new file mode 100644 index 000000000..14fbb4880 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/polling.md @@ -0,0 +1,24 @@ +--- +title: "How to use polling?" +layout: page +tags: +- guide +- advanced +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/guides/reactive-to-imperative.md b/documentation/docs-roq/content/3.2.1/guides/reactive-to-imperative.md new file mode 100644 index 000000000..a659895e3 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/reactive-to-imperative.md @@ -0,0 +1,55 @@ +--- +title: "From reactive to imperative" +layout: page +tags: +- guide +- advanced +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/guides/replaying-multis.md b/documentation/docs-roq/content/3.2.1/guides/replaying-multis.md new file mode 100644 index 000000000..4f6a85f72 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/replaying-multis.md @@ -0,0 +1,92 @@ +--- +title: "Replaying Multis" +layout: page +tags: +- guide +- advanced +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/guides/rx.md b/documentation/docs-roq/content/3.2.1/guides/rx.md new file mode 100644 index 000000000..623e6d360 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/rx.md @@ -0,0 +1,34 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: +- guide +- advanced +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/3.2.1/guides/shortcut-methods.md b/documentation/docs-roq/content/3.2.1/guides/shortcut-methods.md new file mode 100644 index 000000000..71595f955 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/shortcut-methods.md @@ -0,0 +1,45 @@ +--- +title: "Shortcut methods" +layout: page +tags: +- guide +- beginner +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/3.2.1/guides/spies.md b/documentation/docs-roq/content/3.2.1/guides/spies.md new file mode 100644 index 000000000..16731fa80 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/spies.md @@ -0,0 +1,68 @@ +--- +title: "Spying on events" +layout: page +tags: +- guide +- advanced +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/guides/take-skip-items.md b/documentation/docs-roq/content/3.2.1/guides/take-skip-items.md new file mode 100644 index 000000000..cfd0d5fdd --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/take-skip-items.md @@ -0,0 +1,111 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: +- guide +- beginner +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/3.2.1/guides/testing.md b/documentation/docs-roq/content/3.2.1/guides/testing.md new file mode 100644 index 000000000..44b6b66c5 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/testing.md @@ -0,0 +1,30 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: +- guide +- beginner +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/guides/unchecked-exceptions.md b/documentation/docs-roq/content/3.2.1/guides/unchecked-exceptions.md new file mode 100644 index 000000000..acbaef411 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/guides/unchecked-exceptions.md @@ -0,0 +1,41 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: +- guide +- intermediate +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` diff --git a/documentation/docs-roq/content/3.2.1/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/3.2.1/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..618e1e15f --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,76 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: +- reference +- beginner +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` + diff --git a/documentation/docs-roq/content/3.2.1/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/3.2.1/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..10f4af23d --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/reference/migrating-to-mutiny-2.md @@ -0,0 +1,57 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: +- reference +- beginner +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/3.2.1/reference/publications.md b/documentation/docs-roq/content/3.2.1/reference/publications.md new file mode 100644 index 000000000..d3e1050dc --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/reference/publications.md @@ -0,0 +1,21 @@ +--- +title: "Publications" +layout: page +tags: +- reference +- advanced +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/reference/uni-and-multi.md b/documentation/docs-roq/content/3.2.1/reference/uni-and-multi.md new file mode 100644 index 000000000..8ed848766 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/reference/uni-and-multi.md @@ -0,0 +1,45 @@ +--- +title: "Uni and Multi" +layout: page +tags: +- reference +- beginner +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/3.2.1/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/3.2.1/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..ad32827c1 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/reference/what-is-reactive-programming.md @@ -0,0 +1,50 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: +- reference +- beginner +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/3.2.1/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/3.2.1/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..b18fd711f --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/reference/what-makes-mutiny-different.md @@ -0,0 +1,146 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: +- reference +- beginner +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/3.2.1/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/3.2.1/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..f5ab840a7 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/reference/why-is-asynchronous-important.md @@ -0,0 +1,49 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: +- reference +- beginner +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/3.2.1/tags-index.md b/documentation/docs-roq/content/3.2.1/tags-index.md new file mode 100644 index 000000000..ddb9270bc --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/tags-index.md @@ -0,0 +1,26 @@ +--- +title: "Index" +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +[TAGS] \ No newline at end of file diff --git a/documentation/docs-roq/content/3.2.1/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/3.2.1/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..ace90562c --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/tutorials/creating-multi-pipelines.md @@ -0,0 +1,144 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/3.2.1/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/3.2.1/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..55cba201f --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/tutorials/creating-uni-pipelines.md @@ -0,0 +1,121 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/3.2.1/tutorials/getting-mutiny.md b/documentation/docs-roq/content/3.2.1/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/3.2.1/tutorials/handling-failures.md b/documentation/docs-roq/content/3.2.1/tutorials/handling-failures.md new file mode 100644 index 000000000..a672254b1 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/tutorials/handling-failures.md @@ -0,0 +1,90 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/3.2.1/tutorials/hello-mutiny.md b/documentation/docs-roq/content/3.2.1/tutorials/hello-mutiny.md new file mode 100644 index 000000000..fe9a25c0f --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/tutorials/hello-mutiny.md @@ -0,0 +1,72 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/3.2.1/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/3.2.1/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..37175fa90 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](running-workshop-sample.png){ width="400" } diff --git a/documentation/docs-roq/content/3.2.1/tutorials/observing-events.md b/documentation/docs-roq/content/3.2.1/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/3.2.1/tutorials/retrying.md b/documentation/docs-roq/content/3.2.1/tutorials/retrying.md new file mode 100644 index 000000000..6037ca244 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/tutorials/retrying.md @@ -0,0 +1,64 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/3.2.1/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/3.2.1/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..d686f2457 --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,152 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +### Controlling concurrency with merge + +The `merge` method accepts an optional `concurrency` parameter that limits how many inner streams can be subscribed to concurrently: + +```java +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concurrency")} +``` + +When not provided, the default concurrency is configured using `Infrastructure.getBufferSizeS()`. + +> [!WARNING] +> **Merge concurrency** +> +> When using merge with limited concurrency, be aware of potential backpressure issues. +> **Setting concurrency too low** can cause upstream request starvation if the number of subscribed but not emitting inner streams surpasses the level of concurrency. +> **Unbounded concurrency** eliminates the request starvation issue by removing the limit on the number of subscribed inner streams to merge. + + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` + +Like after `transformToUni`, the `merge` method after `transformToMulti` also accepts an optional concurrency parameter with the same considerations regarding backpressure and request starvation when used with infinite streams. diff --git a/documentation/docs-roq/content/3.2.1/tutorials/transforming-items.md b/documentation/docs-roq/content/3.2.1/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/3.2.1/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/3.3.0/guides/branching.md b/documentation/docs-roq/content/3.3.0/guides/branching.md new file mode 100644 index 000000000..55ee70462 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/branching.md @@ -0,0 +1,54 @@ +--- +title: "How to do branching in a reactive pipeline?" +layout: page +tags: +- guide +- intermediate +--- + +# How to do branching in a reactive pipeline? + +Mutiny and similar reactive programming libraries do not have _branching_ operators similar to `if / else` and `switch/case` statements in Java. + +This does not mean that we can't express _branching_ in a reactive pipeline, and the most classic way is to use a transformation to a `Uni` (also called `flatMap` in functional programming). + +## Expressing branches as Uni operations + +Suppose that we have a pipeline where a `Uni` is created from a random value, and suppose that we want to have a different processing pipeline depending on whether the value is odd or even. +Let's have these 2 `Uni`-returning methods to model different behaviors: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "branches")} +``` + +We can use the `transformToUni` operator to plug either method depending on the random number: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "pipeline")} +``` + +Having such a mapping function is a common pattern: it has conditional logic and each branch returns a `Uni` that represents the "sub-pipeline" of what each branch shall do. + +Note that such constructs are primarily relevant when asynchronous I/O are involved and that such asynchronous I/O operations are typically `Uni`-returning methods such as those found in the [Mutiny Vert.x bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/). + +> [!TIP] +> There are other ways to express the "result" of a branch. +> You could wrap results in a custom type or a container like `java.util.Optional`. +> +> You could also return a failed `Uni`, and later react by continuing with another `Uni`, another value, or retrying (which would model a loop!). + +## Branching in a Multi + +The case of `Multi` is even more interesting because a `null`-completed `Uni` is discarded from the stream by any of the `transformToUni\{...}` methods: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "multi-pipeline")} +``` + +where `drop()` is as follows: + +```java linenums="1" +{=snippet:insert("java/guides/BranchingTest.java", "drop")} +``` + +Any negative value is discarded in this `Multi` pipeline, while the positive even and odd numbers get forwarded to the subscriber. diff --git a/documentation/docs-roq/content/3.3.0/guides/broadcasting-to-multiple-subscribers.md b/documentation/docs-roq/content/3.3.0/guides/broadcasting-to-multiple-subscribers.md new file mode 100644 index 000000000..6e5feed8b --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/broadcasting-to-multiple-subscribers.md @@ -0,0 +1,203 @@ +--- +title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" +layout: page +tags: +- guide +- beginner +--- + +# Broadcasting to multiple subscribers (like server-sent events, websockets, etc) + +It is a common requirement in event-driven applications to have multiple subscribers receiving events from a single events source: + +```mermaid +flowchart LR + source[Events source] + proc[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source -- a, b, c --> proc + proc -- a, b, c --> sub1 & sub2 & sub3 +``` + +A good example would be a periodic events stream where the events get pushed every second to multiple [server-sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) subscribers. + +Let us see how this can be done using Mutiny and the [Quarkus framework](https://quarkus.io/). + +> [!NOTE] +> We use Quarkus to illustrate a classic real-world usage for the broadcast operator, but the same kind of construct +> could be done with another framework or the [Vert.x toolkit](https://vertx.io/). + +## Use-case: dynamic subscribers + +We expose a HTTP endpoint for server-sent events, and each second we receive the current list of subscribers. +The endpoint is exposed on path `/hello/ticks/\{id}` where `id` is an identifier for a subscriber. + +We could subscribe and follow the stream using [HTTPie](https://httpie.io/) for subscriber `1`: + +```text +$ http --stream :8080/hello/ticks/1 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1 + +data:1 + +data:1 + +data:1,2 + +data:1,2 +``` + +The first 3 ticks have just one subscriber (`data:1`), but then once another subscriber arrives at path `/hello/ticks/2` we get ticks with identifiers `1,2`. + +When the connection for subscriber `1` closes, we see the impact on subscriber `2`: + +```text +http --stream :8080/hello/ticks/2 +HTTP/1.1 200 OK +Content-Type: text/event-stream +X-SSE-Content-Type: text/plain +transfer-encoding: chunked + +data:1,2 + +data:1,2 + +data:2 + +data:2 + +data:2 +``` + +## Why is broadcasting required? + +Mutiny offers a publisher for periodic event streams: + +```java +var ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)); +``` + +You might wonder why we couldn't simply build our Quarkus endpoints as in: + +```java +@Path("/hello") +public class GreetingResource \{ + + @GET + @Path("ticks/\{id}") + @RestStreamElementType(MediaType.TEXT_PLAIN) + public Multi ticks(String id) \{ + return Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + // (rest of the pipeline...) + ; + } +} +``` + +The problem here is that instead of having a single events source to emit ticks, you will have as many as there are subscribers: + +```mermaid +flowchart LR + source1[Events source] + source2[Events source] + source3[Events source] + proc1[Publisher] + proc2[Publisher] + proc3[Publisher] + sub1[Subscriber 1] + sub2[Subscriber 2] + sub3[Subscriber 3] + source1 -- a, b, c --> proc1 + source2 -- a, b, c --> proc2 + source3 -- a, b, c --> proc3 + proc1 -- a, b, c --> sub1 + proc2 -- a, b, c --> sub2 + proc3 -- a, b, c --> sub3 +``` + +You might be able to articulate some logic around such construct, but you will end up with many scheduled operations on the Quarkus thread pool. + +Even worse: if your source is not periodic ticks emitted by Mutiny but some event source (e.g., a Kafka topic, a message broker queue, etc) then you might add correctness issues (e.g., not all subscribers get all messages). + +## Broadcasting, listening to subscriptions and cancellations + +It is fortunately quite easy to express such construct by sharing a common `Multi`, and broadcasting events to each subscriber. + +Let's start with the HTTP endpoint: + +```java +private ConcurrentHashSet identifiers = new ConcurrentHashSet<>(); + +private Multi ticks = (...); + +@GET +@Path("ticks/\{id}") +@RestStreamElementType(MediaType.TEXT_PLAIN) +public Multi ticks(String id) \{ + Log.info("New client with id " + id); + identifiers.add(id); + return ticks.onCancellation().invoke(() -> \{ + Log.info("Removing client with id " + id); + identifiers.remove(id); + }); +} +``` + +The `ticks` method registers a new client in `identifiers`, and removes it upon cancellation. +The returned `Multi` pipeline piggy-backs on top of the _shared_ `Multi`, which is built as follows: + +```java +private Multi ticks = Multi.createFrom().ticks().every(Duration.ofSeconds(1)) + .onItem().transform(tick -> \{ + Log.info("tick"); + return identifiers.stream().collect(Collectors.joining(",")); + }) + .onSubscription().invoke(() -> Log.info("Starting to emit ticks")) + .onCancellation().invoke(() -> Log.info("No more ticks")) + .broadcast() + .withCancellationAfterLastSubscriberDeparture() + .toAtLeast(1); +``` + +Here are a few observations. + +1. For each periodic tick event, we assemble the current subscribers as a string of the form `"1,2,3"` with `.onItem().transform(...)`. +2. We log an event when the periodic event stream starts (see `onSubscription().invoke(...)`). +3. We log an event when the periodic event stream stops (see `onCancellation().invoke(...)`). +4. We broadcast events to all subscribers, but: + 1. there must be at least one subscriber before the stream starts, and + 2. the stream is cancelled when the last subscriber departs. + +This construction is quite interesting because we don't emit ticks when there are no subscribers, and we stop it when there are none. + +If you play with such an example then you will see logs similar to these: + +```text +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) New client with id 1 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Starting to emit ticks +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) New client with id 2 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-1) Removing client with id 1 +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (executor-thread-1) tick +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) Removing client with id 2 +INFO [org.acm.GreetingResource] (vert.x-eventloop-thread-0) No more ticks +``` diff --git a/documentation/docs-roq/content/3.3.0/guides/collecting-items.md b/documentation/docs-roq/content/3.3.0/guides/collecting-items.md new file mode 100644 index 000000000..21e9545dc --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/collecting-items.md @@ -0,0 +1,97 @@ +--- +title: "Collecting items from Multi" +layout: page +tags: +- guide +- beginner +--- + +# Collecting items from Multi + +There are cases where you want to accumulate the items from a `Multi` and produce a `Uni` as a final result. +This is also called a _reduction_ in functional programming. + +The `Uni` fires its item when the `Multi` completes. +Mutiny provides multiple operators to deal with that scenario. +They are available from the `collect()` group. +For example, you can store the items in a list, emit the list on completion, or use a Java `Collector` to customize the aggregation. + +> [!CAUTION] +> Don't collect items from infinite streams or you will likely end with an out-of-memory failure! + +## Collecting items into a list + +One of the most common approaches to collect items is to store them in a list (`Uni>`) +It emits the final list when the `Multi` completes. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Collect operator + participant D as Subscriber + + M->>O: onItem(1) + M->>O: onItem(2) + M->>O: onItem(3) + + O->>D: onItem([1, 2, 3]) +``` + +How to achieve this with Mutiny? + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "list")} +``` + +It's important to note that the returned type is a `Uni`. +It emits the list when the multi completes. + +## Collecting items into a map + +You can also collect the items into a `Map`. +In this case, you need to provide a function to compute the key for each item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "map")} +``` + +If the key mapper function returns the same key for multiple items, the last one with that key is stored in the final `Map`. +You can collect items in a _multimap_ to handle items with the same keys. + +## Collecting items into a multimap + +A multimap is a `Map>.` +In the case of a conflicting key, it stores all the items in a list associated with that key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "multimap")} +``` + +## Using a custom accumulator + +You can also use a custom _accumulator_ function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "accumulator")} +``` + +The `in` method receives two arguments: + +1. a supplier providing the new instance of your collection/container +2. the accumulator function + +You can also use a Java `Collector`. +For example, in the next example, count the number of items, and produce the final count as item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "collector")} +``` + +## Getting the first and last items + +While they are not strictly speaking collecting items, `collect().first()` and `collect().last()` allow retrieving the first and last item from a `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CollectingItemsTest.java", "first")} +``` diff --git a/documentation/docs-roq/content/3.3.0/guides/combining-items.md b/documentation/docs-roq/content/3.3.0/guides/combining-items.md new file mode 100644 index 000000000..19ac573b2 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/combining-items.md @@ -0,0 +1,158 @@ +--- +title: "Combining items from streams" +layout: page +tags: +- guide +- intermediate +--- + +# Combining items from streams + +Combining items from various streams is an essential pattern in Reactive Programming. + +It associates the emitted items from multiple streams and emits an _aggregate_. +The downstream receives this _aggregate_ and can handle it smoothly. + +There are plenty of use cases, such as executing two tasks concurrently and waiting for both completions, getting the last items from different streams to build an always up-to-date view, and so on. + +## Combining Unis + +Imagine that you have two asynchronous operations to perform like 2 HTTP requests. +You want to send these requests and be notified when both have completed with their responses ready to be consumed. + +Of course, you could send the first request, wait for the response, and then send the second request. +If both requests are independent, we can do something better: send both concurrently and await for both completions! + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(2) + B->>M: onItem(b) + + M->>S: onItem([2,b]) +``` + +How can you achieve this with Mutiny? + +First, each request is a `Uni`, so we have: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +``` + +Then, we want to combine both _responses_: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +``` + +This code creates a new `Uni` produced by combining `uniA` and `uniB`. +The responses are aggregated inside a `Tuple`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +The `tuple` aggregates the responses in the same order as the `Uni` sequence. + +If one of the `Uni` fails, so does the combination and you receive the failure: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "invocations")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combination")} +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "subscription")} +``` + +Using tuples is convenient but only works if you have less than 10 `Uni` objects. +If you want another structure or deal with 10 `Uni` objects or more then use `combineWith`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combined-with")} +``` + +## Combining Multis + +Combining `Multis` consists of associating items from different stream per _index_: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([1,a]) + + A->>M: onItem(3) + B->>M: onItem(b) + M->>S: onItem([2,b]) + + B->>M: onItem(c) +``` + +It associates the first items from the combined streams, then the second items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi")} +``` + +As for `Uni`, you can aggregate the item into tuples (up to 9 items) or combine with a combinator function: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-multi-with")} +``` + +If one of the streams fails, the combined stream propagates the failure and stops the emission. +The combined stream completes as soon as one of the observed stream sends the completion event. + +> [!NOTE] +> If one of the observed streams never emits any item then the combined stream will not emit anything. + +## Combining the latest items of Multis + +It can be useful to combine multiple `Multi` streams and receive the _latest_ items from each stream on every emission: + + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Combined stream + participant S as Subscriber + + A->>M: onItem(1) + A->>M: onItem(2) + B->>M: onItem(a) + + M->>S: onItem([2,a]) + + A->>M: onItem(3) + M->>S: onItem([3,a]) + B->>M: onItem(b) + M->>S: onItem([3,b]) + + B->>M: onItem(c) + M->>S: onItem([3,c]) +``` + +This is achieved using `latest()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/CombiningItemsTest.java", "combine-last")} +``` diff --git a/documentation/docs-roq/content/3.3.0/guides/completion-stage.md b/documentation/docs-roq/content/3.3.0/guides/completion-stage.md new file mode 100644 index 000000000..976c20cac --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/completion-stage.md @@ -0,0 +1,83 @@ +--- +title: "How to deal with CompletionStage?" +layout: page +tags: +- guide +- intermediate +--- + +# How to deal with CompletionStage? + +`CompletionStage` and `CompletableFuture` are classes provided by Java to represent asynchronous actions. + +## Differences between Uni and CompletionStage + +While `CompletionStage` and `CompletableFuture` are close to `Uni` in terms of use case, there are some fundamental differences. + +`CompletionStage` are _eager_. +When a method returns a `CompletionStage,` the operation has already been triggered. +The outcome is used to complete the returned `CompletionStage`. +On the other side, `Unis` are lazy. +The operation is only triggered once there is a subscription. + +`CompletionStage` _caches_ the outcome. +So, once received, you can retrieve the result. +Every retrieval will get the same result. +With `Uni`, every subscription has the opportunity to re-trigger the operation and gets a different result. + +> [!TIP] +> You can also _cache_ the outcome with `Uni.memoize().indefinitely()`. + +## From Uni to CompletionStage + +You can create a `CompletionStage` from `Uni` using `uni.subscribeAsCompletionStage()`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs")} +``` + +It's important to understand that retrieving a `CompletionStage` subscribes to the `Uni`. +If you do this operation twice, it subscribes to the `Uni` twice and re-trigger the operation. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "uni-subscribe-cs-twice")} +``` + +## Creating a Uni from a CompletionStage + +To create a `Uni` from a `CompletionStage`, use `Uni.createFrom().completionStage(...)`. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-uni")} +``` + +As you can see, there are two versions. +The first one receives the `CompletionStage` directly, while the second one gets a supplier. +In the case of multiple subscriptions on the produced `Uni`, the supplier is called multiple times (once per subscription), and so can change the return `CompletionStage`. +It also delays the creation of the `CompletionStage` until there is a subscription, which only triggers the operation at that time. +If you pass the instance directly, it will always use the same one (even for multiple subscriptions) and triggers the operation even if there is no subscription. +For these reasons, it is generally better to use the variant accepting a supplier. + +Note that if the completion stage produces a `null` value, the resulting `Uni` emits `null` as item. +If the completion stages complete exceptionally, the failure is emitted by the resulting `Uni`. + +## Creating a Multi from a CompletionStage + +To create a `Multi` from a `CompletionStage`, use `Multi.createFrom().completionStage(...)`. +It produces: + +* a multi emitting an item and completing - if the value produced by the completion stage is not `null`, +* an empty multi if the value produced by the completion stage is `null`, +* a failed multi is completion stage is completed exceptionally. + +```java linenums="1" +{=snippet:insert("java/guides/CompletionStageTest.java", "create-multi")} +``` + +For the same reason as for `Uni`, there are two versions: + +1. one accepting a `CompletionStage` directly +2. one accepting a `Supplier`, called at subscription-time, for every subscription. + +It is recommended to use the second version. + diff --git a/documentation/docs-roq/content/3.3.0/guides/context-passing.md b/documentation/docs-roq/content/3.3.0/guides/context-passing.md new file mode 100644 index 000000000..de4b3d027 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/context-passing.md @@ -0,0 +1,82 @@ +--- +title: "Context passing" +layout: page +tags: +- guide +- advanced +--- + +# Context passing + +Mutiny reactive pipelines let data flow from publishers to subscribers. + +In the vast majority of cases a publisher shall have _all_ required data, and operators shall perform processing based on item values. +For instance a network request shall be made with all request data known in advance, and response processing shall only depend on the response payload. + +That being said there are cases were this is not sufficient, and some data has to be carried along with items. +For instance one intermediary operator in a pipeline may have to make another networked request from which we need to extract some correlation identifier which will be used by another operator down the pipeline. +In such cases one will be tempted to forward tuples consisting of some item value plus some "extra" data. + +For such cases Mutiny offers a _subscriber-provided context_, so all operators involved in a subscription can share some form of _implicit data_. + +## What's in a context? + +A context is a simple key / value, in-memory storage. +Data can be queried, added and deleted from a context, as shown in the following snippet: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextManipulation")} +``` + +`Context` objects are thread-safe, and can be created from sequences of key / value pairs (as shown above), from a Java `Map`, or they can be created empty. + +Note that an empty-created context defers its internal storage allocation until the first call to `put`. +You can see `Context` as a glorified `ConcurrentHashMap` delegate, although this is an implementation detail and Mutiny might explore various internal storage strategies in the future. + +> [!TIP] +> Contexts shall be primarily used to share transient data used for networked I/O processing such as correlation identifiers, tokens, etc. +> +> They should not be used as general-purpose data structures that are frequently updated and that hold large amounts of data. + +## How to access a context? + +Given a `Uni` or a `Multi`, a context can be accessed using the `withContext` operator, as in: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextSampleUsage")} +``` + +This operator builds a sub-pipeline using 2 parameters: the current `Uni` or `Multi` and the context. + +> [!IMPORTANT] +> The function passed to `withContext` is called at subscription time. +> +> This means that the context has not had a chance to be updated by upstream operators yet, so be careful with what you do in the body of that function. + +There is another way to access the context by using the `attachContext` method: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "contextAttachedSampleUsage")} +``` + +This method materializes the context in the regular pipeline items using the wrapper `ItemWithContext` class. +The `get` method provides the item while the `context` method provides the context. + +## How to access a context at the pipeline source? + +The `Uni` and `Multi` _builder_ methods like `Multi.createFrom()` provide publishers, not operators, so they don't have the `withContext` method. + +The first option is to use the `Uni.createFrom().context(...)` or `Multi.createFrom().context(...)` general purpose method to materialize the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "builderUsage")} +``` + +The `context` method takes a function that accepts a `Context` and returns a pipeline. +This is very similar to the `deferred` builder. + +If you use an `emitter` builder then for both `Uni` and `Multi` cases the emitter object offers a `context` method to access the context: + +```java linenums="1" +{=snippet:insert("java/guides/ContextPassingTest.java", "emitterUsage")} +``` diff --git a/documentation/docs-roq/content/3.3.0/guides/controlling-demand.md b/documentation/docs-roq/content/3.3.0/guides/controlling-demand.md new file mode 100644 index 000000000..ed21844c3 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/controlling-demand.md @@ -0,0 +1,155 @@ +--- +title: "Controlling the demand" +layout: page +tags: +- guide +- advanced +--- + + +# Controlling the demand + +## Pacing the demand + +A subscription is used for 2 purposes: cancelling a request and demanding batches of items. + +The `Multi.paceDemand()` operator can be used to automatically issue requests at certain points in time. + +The following example issues requests of 25 items every 100ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "pacing")} +``` + +`FixedDemandPacer` is a simple _pacer_ with a fixed demand and a fixed delay. + +You can create more elaborated pacers by implementing the `DemandPacer` interface. +To do so you provide an initial request and a function to evaluate the next request which is evaluated based on the previous request and the number of items emitted since the last request: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "custom-pacer")} +``` + +The previous example is a custom pacer that doubles the demand and increases the delay for each new request. + +## Capping the demand requests + +The `capDemandsTo` and `capDemandUsing` operators can be used to cap the demand from downstream subscribers. + +The `capDemandTo` operator defines a maximum demand that can flow: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capConstant")} +``` + +Here we cap requests to 50 items, so it takes 2 requests to get all 100 items of the upstream range. +The first request of 75 items is capped to a request of 50 items, leaving an outstanding demand of 25 items. +The second request of 25 items is added to the outstanding demand, resulting in a request of 50 items and completing the stream. + +You can also define a custom function that provides a capping value based on a custom formula, or based on earlier demand observations: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ControlDemandTest.java", "capFunction")} +``` + +Here we have a function that requests 75% of the downstream requests. + +Note that the function must return a value `n` that satisfies `(0 < n <= requested)` where `requested` is the downstream demand. + +## Pausing the demand + +The `Multi.pauseDemand()` operator provides fine-grained control over demand propagation in reactive streams. +Unlike cancellation, which terminates the subscription, pausing allows to suspend demand without unsubscribing from the upstream. +This is useful for implementing flow control patterns where item flow needs to be paused based on external conditions. + +### Basic pausing and resuming + +The `pauseDemand()` operator works with a `DemandPauser` handle that allows to control the stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "basic")} +``` + +The `DemandPauser` provides methods to: + +- `pause()`: Stop propagating demand to upstream +- `resume()`: Resume demand propagation and deliver buffered items +- `isPaused()`: Check the current pause state + +Note that a few items may still arrive after pausing due to in-flight requests that were already issued to upstream. + +### Starting in a paused state + +You can create a stream that starts paused and only begins flowing when explicitly resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "initially-paused")} +``` + +This is useful when you want to prepare a stream but delay its execution until certain conditions are met. + +### Late subscription + +By default, the upstream subscription happens immediately even when starting paused. +The `lateSubscription()` option delays the upstream subscription until the stream is resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "late-subscription")} +``` + +### Buffer strategies + +When a stream is paused, the operator stops requesting new items from upstream. +However, items that were already requested (due to downstream demand) may still arrive. +Buffer strategies control what happens to these in-flight items. + +The `pauseDemand()` operator supports three buffer strategies: `BUFFER` (default), `DROP`, and `IGNORE`. +Configuring any other strategy will throw an `IllegalArgumentException`. + +#### BUFFER strategy (default) + +Already-requested items are buffered while paused and delivered when resumed: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "buffer-strategy")} +``` + +You can configure the buffer size: + +- `bufferUnconditionally()`: Unbounded buffer +- `bufferSize(n)`: Buffer up to `n` items, then fail with buffer overflow + +When the buffer overflows, the stream fails with an `IllegalStateException`. + +**Important**: The buffer only holds items that were already requested from upstream before pausing. +When paused, no new requests are issued to upstream, so the buffer size is bounded by the outstanding demand at the time of pausing. + +#### DROP strategy + +Already-requested items are dropped while paused: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "drop-strategy")} +``` + +Items that arrive while paused are discarded, and when resumed, the stream continues requesting fresh items. + +#### IGNORE strategy + +Already-requested items continue to flow downstream while paused. +This strategy doesn't use any buffers. +It only pauses demand from being issued to upstream, but does not pause the flow of already requested items. + +### Buffer management + +When using the BUFFER strategy, you can inspect and manage the buffer: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PausingDemandTest.java", "buffer-management")} +``` + +The `DemandPauser` provides: + +- `bufferSize()`: Returns the current number of buffered items +- `clearBuffer()`: Clears the buffer (only works while paused), returns `true` if successful + diff --git a/documentation/docs-roq/content/3.3.0/guides/converters.md b/documentation/docs-roq/content/3.3.0/guides/converters.md new file mode 100644 index 000000000..e6069e3c2 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/converters.md @@ -0,0 +1,216 @@ +--- +title: "Using other reactive programming libraries" +layout: page +tags: +- guide +- advanced +--- + +# Using other reactive programming libraries + +You may need to integrate libraries exposing an API using other reactive programming libraries such as RX Java or Reactor. +Mutiny has a built-in conversion mechanism to ease that integration. + +## Picking the right dependency + +You need to add another dependency to access the converters. +Each artifact contains the converters for a specific reactive library. +Pick the right one and add it to your project: + +#### Reactor + +```xml + + + io.smallrye.reactive + mutiny-reactor + {=cdi:attributes.versions.mutiny} + +``` + +#### RxJava 3 + +```xml + + + io.smallrye.reactive + mutiny-rxjava3 + {=cdi:attributes.versions.mutiny} + +``` + +## Integration with Project Reactor + +[Project Reactor](https://projectreactor.io/) is a popular reactive programming library. +It offers two types: `Mono` and `Flux,` both implementing Reactive Stream `Publisher`. + +To use the Reactor `<->` Mutiny converter, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiReactorConverters; +import io.smallrye.mutiny.converters.uni.UniReactorConverters; +``` + +### Converting a Flux or a Mono into a Multi + +Both `Flux` and `Mono` implement `Publisher`. +As a result, we can use the Reactive Streams interoperability to convert instances from `Flux` and `Mono` to `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-multi-create")} +``` + +> [!WARNING] +> Reactor still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. +> +> We recommend using the [Mutiny Zero Flow Adapters library](https://smallrye.io/smallrye-mutiny-zero/) as in these examples (Maven coordinates `io.smallrye.reactive:mutiny-zero-flow-adapters`). + +### Converting a Flux or a Mono into a Uni + +As you can create `Uni` from a `Publisher`, the same approach can be used to create `Uni` instances: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-uni-create")} +``` + +When a `Flux` or `Mono` sends the _completion_ event without having emitted any item, the resulting `Uni` emits `null`. + +When converting a `Flux` to `Uni`, the resulting `Uni` emits the first item. +After that emission, it cancels the subscription to the `Flux`. + +### Converting a Multi into a Flux or Mono + +Converting a `Multi` into a `Flux` or a `Mono` uses the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-multi")} +``` + +### Converting a Uni into a Flux or Mono + +Converting a `Uni` into a `Flux` or a `Mono` requires a converter, as `Uni` does not implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "reactor-create-uni")} +``` + +If the `Uni` emits `null`, it sends the _completion_ event. + +### Using converter instead of Reactive Streams + +While Reactive Streams interoperability is convenient, Mutiny also provides converters to create `Flux` and `Mono` from `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactorTest.java", "uni-export")} +{=snippet:insert("java/guides/integration/ReactorTest.java", "multi-export")} +``` + +## Integration with RX Java 3 + +RxJava is another popular reactive programming library. +It offers 5 types: `Completable` (no item), `Single` (one item), `Maybe` (0 or 1 item), `Observable` (multiple items), `Flowable` (multiple items, implements Reactive Stream `Publisher`). + +To use the RxJava `<->` Mutiny converters, add the following imports to your class: + +```java +import io.smallrye.mutiny.converters.multi.MultiRx3Converters; +import io.smallrye.mutiny.converters.uni.UniRx3Converters; +``` + +### Converting an Observable or a Flowable into a Multi + +Both `Observable` and `Flowable` are item streams. +However, `Observable` does not implement `Publisher` and so does not have back-pressure support. + +To create `Multi` from an `Observable,` you need a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-observable")} +``` + +Converting a `Flowable` is easier, as it's a `Publisher`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-flowable")} +``` + +> [!WARNING] +> Like Reactor, RxJava still uses the legacy Reactive Streams APIs instead of `java.util.concurrent.Flow`, so you need to perform an adaptation. + +### Converting a Completable, Single or Maybe into a Multi + +To create a `Multi` from a `Completable,` `Single` or `Maybe` you need specific converters, as none of these types implement Reactive Streams. + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-multi-create-single")} +``` + +- Creating a `Multi` from a `Completable` always produces a `Multi` that only emits the _completion_ or _failure_ event. +- Creating a `Multi` from a `Single` produces a `Multi`. That `Multi` emits the item and then completes it. +- Creating a `Multi` from a `Maybe` produces a `Multi`. That `Multi` emits the item (if any) and then completes it. + If the `Maybe` is empty, then the created `Multi` emits the _completion_ event. + +When a `Completable,` `Single,` or `Maybe` emits a failure, then the resulting `Multi` emits that failure. + +### Converting an Observable or a Flowable into a Uni + +To create a `Uni` from an `Observable,` you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-observable")} +``` + +The creation from a `Flowable` can be done using the Reactive Streams interoperability: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-flowable")} +``` + +In both cases, it cancels the subscription to the `Flowable` or `Observable` after receiving the first item. +If the `Flowable` or `Observable` completes without items, the `Uni` emits a `null` item. + +### Converting a Completable, Single or Maybe into a Uni + +To create a `Uni` from a `Completable,` `Single,` or `Maybe`, you need to use a specific converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "rx-uni-create-single")} +``` + +Converting a `Completable` to a `Uni` always produces a `Uni,` that emits either `null` once the `Completable` completes or the failure if it fails. +The `Maybe` to `Uni` conversion emits a `null` item if the `Maybe` completes without an item. + +### Converting a Multi into a RX Java objects + +The conversion from a `Multi` to the various RX Java objects is done using converters: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-multi")} +``` + +The creation of a `Completable` from a `Multi` discards all the items emitted by the `Multi`. +It only forwards the _completion_ or _failure_ event. + +Converting a `Multi` into a `Single` returns a `Single>,` as the `Multi` may complete without items. +You can also produce a `Single` and emit a _failure_ event if the `Multi` completes without items. +You can configure the thrown exception using `onEmptyThrow.` + +> [!TIP] +> You can also create a `Flowable` from a `Multi` using: `Flowable.fromPublisher(multi)`. + +### Converting a Uni into a RX Java type + +Similarly to the conversion from a `Multi` into an RX Type, converting a `Uni` requires a converter: + +```java linenums="1" +{=snippet:insert("java/guides/integration/RxJavaTest.java", "create-rx-from-uni")} +``` + +The creation of a `Completable` from a `Uni` discards the item and sends the _completion_ signal after emission. + +Converting a `Uni` into a `Single` returns a `Single>,` as the `Uni` may emit `null.` +You can also produce a `Single` and emits a _failure_ event if the `Uni` sends `null.` +Configure the failure to forward using `failOnNull.` + +The creation of a `Maybe,` `Flowable,` or an `Observable` from a `Uni` produces an empty `Maybe,` `Flowable,` or `Observable` if the `Uni` emits `null.` +For `Flowable` and `Observable,` if the `Uni` emits a _non-null_ item, that item is emitted, followed immediately by the _completion_ signal. diff --git a/documentation/docs-roq/content/3.3.0/guides/custom-operators.md b/documentation/docs-roq/content/3.3.0/guides/custom-operators.md new file mode 100644 index 000000000..54f891fd2 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/custom-operators.md @@ -0,0 +1,30 @@ +--- +title: "Can I have custom operators?" +layout: page +tags: +- guide +- advanced +--- + +# Can I have custom operators? + +Yes, but please write operators responsibly! + +Both `Uni` and `Multi` support custom operators using the `plug` operator. +Here is an example where we use a custom `Multi` operator that randomly drops items: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "plug")} +``` + +with the operator defined as follows: + +```java linenums="1" +{=snippet:insert("java/guides/PlugTest.java", "custom-operator")} +``` + + +> [!CAUTION] +> Custom operators are an advanced feature: when possible please use the existing operators and use helpers such as `stage` to write readable code. +> +> In the case of custom `Multi` operators it is wise to test them against the _Reactive Streams TCK_. diff --git a/documentation/docs-roq/content/3.3.0/guides/delaying-events.md b/documentation/docs-roq/content/3.3.0/guides/delaying-events.md new file mode 100644 index 000000000..a225c1f66 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/delaying-events.md @@ -0,0 +1,64 @@ +--- +title: "How to delay events?" +layout: page +tags: +- guide +- intermediate +--- + +# How to delay events? + +## Delaying Uni's item + +When you have a `Uni`, you can delay the item emission using `onItem().delayIt().by(...)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-by")} +``` + +You pass a duration. +When the item is received, it _waits for_ that duration before propagating it to the downstream consumer. + +You can also delay the item's emission based on another _companion_ `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-until")} +``` + +The item is propagated downstream when the `Uni` returned by the function emits an item (possibly `null`). +If the function emits a failure (or throws an exception), this failure is propagated downstream. + +## Throttling a Multi + +Multi does not have a _delayIt_ operator because applying the same delay to all items is rarely what you want to do. +However, there are several ways to apply a delay in a `Multi`. + +First, you can use the `onItem().call()`, which delays the emission until the `Uni` produced the `call` emits an item. +For example, the following snippet delays all the items by 10 ms: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi")} +``` + +In general, you don't want to apply the same delay to all the items. +You can combine `call` with a random delay as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "delay-multi-random")} +``` + +Finally, you may want to throttle the items. +For example, you can introduce a (minimum) one-second delay between each item. +To achieve this, combine `Multi.createFrom().ticks()` and the multi to throttled: + +```java linenums="1" +{=snippet:insert("java/guides/operators/DelayTest.java", "throttling-multi")} +``` + +> [!TIP] +> The `onOverflow().drop()` is used to avoid the _ticks_ to fail if the other stream (`multi`) is too slow. + +## Delaying other types of events + +We have looked at how to delay items, but you may need to delay other events, such as subscription or failure. +For these, use the `call` approach, and return a `Uni` that delay the event's propagation. diff --git a/documentation/docs-roq/content/3.3.0/guides/dropped-exceptions.md b/documentation/docs-roq/content/3.3.0/guides/dropped-exceptions.md new file mode 100644 index 000000000..3c9290938 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/dropped-exceptions.md @@ -0,0 +1,29 @@ +--- +title: "How to deal with dropped exceptions?" +layout: page +tags: +- guide +- advanced +--- + +# How to deal with dropped exceptions? + +There are a few corner cases where Mutiny cannot propagate an exception to a `Uni` or a `Multi` subscriber. + +Consider the following example: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "code")} +``` + +The `onCancellation().call(...)` method is called when the `Uni` subscription is cancelled. +The returned `Uni` is failed with a `IOException`, but since the subscription itself has been cancelled then there is no way to catch the exception. + +By default Mutiny reports such dropped exceptions to the standard error stream along with the corresponding stack trace. +You can change how these exceptions are handled using `Infrastructure.setDroppedExceptionHandler`. + +The following logs dropped exceptions to a logger: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/DroppedExceptionTest.java", "override-handler")} +``` diff --git a/documentation/docs-roq/content/3.3.0/guides/eliminate-duplicates-and-repetitions.md b/documentation/docs-roq/content/3.3.0/guides/eliminate-duplicates-and-repetitions.md new file mode 100644 index 000000000..8e0918256 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/eliminate-duplicates-and-repetitions.md @@ -0,0 +1,49 @@ +--- +title: "Eliminate duplicates and repetitions" +layout: page +tags: [guide, beginner] +--- + +# Eliminate duplicates and repetitions + +When observing a `Multi`, you may see duplicated items or repetitions. +The `multi.select()` and `multi.skip()` groups provide methods to only select distinct items or drop repetitions. + +## Selecting distinct + +The `.select().distinct()` operator removes all the duplicates. +As a result, the downstream only contains distinct items: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "distinct")} +``` + +If you have a stream emitting the `\{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.select().distinct()` on such a stream produces: `\{1, 2, 3, 4, 5, 6}`. + +> [!IMPORTANT] +> The operator keeps a reference on all the emitted items, and so, it could lead to memory issues if the stream contains too many distinct items. + +> [!TIP] +> By default, `select().distinct()` uses the `hashCode` method from the item's class. +> You can pass a custom comparator for more advanced checks. + +If you have a stream emitting items of type `T`, where duplicates can be identified through an attribute of `T` of type `K`, +then an `extractor` of type `Function` can be defined. Applying `.select().distinct(extractor)` on such a stream will +eliminate duplicates but have a lesser memory overhead as only the references to the extracted keys need to be kept, not the whole object. +A typical usage of this might be for a stream of records where uniqueness is determined by a UUID assigned to every record. + +## Skipping repetitions + +The `.skip().repetitions()` operator removes subsequent repetitions of an item: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RepetitionsTest.java", "repetition")} +``` + +If you have a stream emitting the `\{1, 1, 2, 3, 4, 5, 5, 6, 1, 4, 4}` items, then applying `.skip().repetitions()` on such a stream produces: `\{1, 2, 3, 4, 5, 6, 1, 4}`. + +Unlike `.select().distinct()`, you can use this operator on large or infinite streams. + +> [!TIP] +> By default, `skip().repetitions()` uses the `equals` method from the item's class. +> You can pass a custom comparator for more advanced checks. diff --git a/documentation/docs-roq/content/3.3.0/guides/emission-threads.md b/documentation/docs-roq/content/3.3.0/guides/emission-threads.md new file mode 100644 index 000000000..fd7816881 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/emission-threads.md @@ -0,0 +1,29 @@ +--- +title: "How to change the emission thread?" +layout: page +tags: [guide, intermediate] +--- + +# How to change the emission thread? + +Except indicated otherwise, Mutiny invokes the next _stage_ using the thread emitting the event from upstream. +So, in the following code, the _transform_ stage is invoked from the thread emitting the event. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "example")} +``` + +You can switch to another thread using the `emitOn` operator. +The `emitOn` operator lets you switch the thread used to dispatch (upstream -> downstream) events, so items, failure and completion events. +Just pass the _executor_ you want to use. + +```java linenums="1" +{=snippet:insert("java/guides/operators/EmitOnTest.java", "code")} +``` + +> [!NOTE] +> You cannot pass a specific thread, but you can implement a simple `Executor` dispatching on that specific thread, or use a _single threaded executor_. + +> [!WARNING] +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. diff --git a/documentation/docs-roq/content/3.3.0/guides/emit-on-vs-run-subscription-on.md b/documentation/docs-roq/content/3.3.0/guides/emit-on-vs-run-subscription-on.md new file mode 100644 index 000000000..fdcedfaac --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/emit-on-vs-run-subscription-on.md @@ -0,0 +1,64 @@ +--- +title: "What is the difference between emitOn and runSubscriptionOn?" +layout: page +tags: [guide, intermediate] +--- + +# What is the difference between emitOn and runSubscriptionOn? + +The `emitOn` and `runSubscriptionOn` are 2 operators influencing on which threads the event are dispatched. +However, they target different types of events and different directions. + +## The case of emitOn + +`emitOn` takes events coming from upstream (items, completion, failure) and replays them downstream on a thread from the given executor. +Consequently, it affects where the subsequent operators execute (until another `emitOn` is used): + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "emitOn")} +``` + +The previous code produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as emitOn(executor) + participant D as Subscriber + + M->>O: onItem(1) + Note right of M: On caller thread + + O->>D: onItem(1) + Note right of O: On executor thread +``` + +> [!WARNING] +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + +## The case of runSubscriptionOn + +`runSubscriptionOn` applies to the subscription process. +It requests the upstream to run its subscription (call of the `subscribe` method on its own upstream) on a thread from the given executor: + +```java linenums="1" +{=snippet:insert("java/guides/operators/RunSubscriptionOnTest.java", "runSubscriptionOn")} +``` + +So, if we consider the previous code snippet, it produces the following sequence: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as runSubscriptionOn(executor) + participant D as Subscriber + + D->>O: subscribe + Note right of O: on Caller thread + + O->>M: subscribe + Note right of M: On executor thread +``` diff --git a/documentation/docs-roq/content/3.3.0/guides/filtering-items.md b/documentation/docs-roq/content/3.3.0/guides/filtering-items.md new file mode 100644 index 000000000..240519721 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/filtering-items.md @@ -0,0 +1,34 @@ +--- +title: "Filtering items from Multi" +layout: page +tags: [guide, beginner] +--- + +# Filtering items from Multi + +When observing a `Multi`, you may not want to forward all the received items to the downstream. + +Use the `multi.select()` group to select items. + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "filter")} +``` + +To _select_ items passing a given predicate, use `multi.select().where(predicate)`: + +`where` accepts a predicate called for each item. +If the predicate returns `true`, the item propagated downstream. +Otherwise, it drops the item. + +The predicate passed to `where` is synchronous. +The `when` method provides an asynchronous version: + +```java linenums="1" +{=snippet:insert("java/guides/operators/FilterTest.java", "test")} +``` + +`when` accepts a function called for each item. + +Unlike `where` where the predicate returns a boolean synchronously, the function returns a `Uni`. +It forwards the item downstream if the `uni` produced by the function emits `true`. +Otherwise, it drops the item. diff --git a/documentation/docs-roq/content/3.3.0/guides/framework-integration.md b/documentation/docs-roq/content/3.3.0/guides/framework-integration.md new file mode 100644 index 000000000..64dc2fc37 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/framework-integration.md @@ -0,0 +1,29 @@ +--- +title: "How can I integrate Mutiny with my framework?" +layout: page +tags: [guide, advanced] +--- + +# How can I integrate Mutiny with my framework? + +Sometimes, Mutiny needs to execute tasks on other threads, such as monitoring time or delaying actions. +Most operators relying on such capacity let you pass either a `ScheduledExecutorService` or an `ExecutorService`. + +By default, Mutiny uses the a _cached_ thread pool as default executor, that creates new threads as needed, but reuse previously constructed threads when they are available. +A `ScheduledExecutorService` is also created but delegates the execution of the delayed/scheduled tasks to the default executor. + +In the case you want to integrate Mutiny with a thread pool managed by a platform, you can configure it using `Infrastructure.setDefaultExecutor()` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "infra")} +``` + +You can configure the default executor using the `Infrastructure.setDefaultExecutor` method: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/InfrastructureTest.java", "set-infra")} +``` + +> [!TIP] +> If you are using Quarkus, the default executor is already configured to use the Quarkus worker thread pool. +> Logging is also configured correctly. diff --git a/documentation/docs-roq/content/3.3.0/guides/grouping-items.md b/documentation/docs-roq/content/3.3.0/guides/grouping-items.md new file mode 100644 index 000000000..f77e5eff1 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/grouping-items.md @@ -0,0 +1,143 @@ +--- +title: "Grouping items from Multi" +layout: page +tags: [guide, intermediate] +--- + +# Grouping items from Multi + +Mutiny provides several operators to group items from a `Multi` stream. +You can group items by a key function (similar to SQL's `GROUP BY`), split items into fixed-size chunks, or create time-based windows. + +The grouping operators are available from the `group()` method on `Multi`. + +## Grouping into Lists + +The `group().intoLists()` operator allows you to collect items into lists based on size or time. + +### Fixed-size lists + +Use `group().intoLists().of(size)` to create fixed-size lists from the stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupIntoLists")} +``` + +The last list may contain fewer items if the stream doesn't divide evenly. + +### Time-based lists + +You can create time-based lists using `group().intoLists().every(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "timeBasedListGrouping")} +``` + +### Size and time-based lists + +You can combine both size and time constraints using `group().intoLists().of(size, Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "sizeAndTimeBasedListGrouping")} +``` + +This will emit a list when either the size limit is reached or the duration expires, whichever comes first. + +## Grouping into Multi Streams + +The `group().intoMultis()` operator allows you to create separate `Multi` streams from your data. Unlike `intoLists()` which materializes all items into memory, `intoMultis()` keeps items as streams, which is better for: + +- Applying stream transformations to each group +- Processing large groups without loading everything into memory +- Composing with other reactive operators + +### Fixed-size Multi streams + +Use `group().intoMultis().of(size)` to create `Multi` streams of a fixed size: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupIntoMultis")} +``` + +### Time-based Multi streams + +You can create time-based windows using `group().intoMultis().every(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "timeBasedGrouping")} +``` + +## Grouping by a key function + +The `group().by()` operator groups items based on a key function, emitting a `Multi>` where each `GroupedMulti` represents a group of items sharing the same key. + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByKey")} +``` + +Each `GroupedMulti` has a `key()` method that returns the key for that group. +Items are distributed to groups based on the key function result. + +### Using both key and value mappers + +You can transform items while grouping them by providing both a key mapper and a value mapper: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByKeyAndValue")} +``` + + +## Processing groups with merge vs concatenate + +When processing groups, you need to decide how to combine the results back into a single stream. Similar to [transforming items asynchronously](../tutorials/transforming-items-asynchronously.md), you can use either **merge** or **concatenate**: + +### Using merge + +With merge, groups are processed concurrently - items from different groups can interleave in the output stream: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByWithMerge")} +``` + +> [!WARNING] +> **Upstream request starvation with merge** +> +> When using `.merge(concurrency)` or similar merge operations after `group().by()`, +> **the concurrency parameter must be greater than or equal to the number of groups that are created but not terminated**. +> +> If you create more groups than the concurrency limit allows, some groups cannot make progress while waiting for others to complete. +> This leads to a **request starvation** where the upstream won't receive requests for emitting new items. + +#### Example causing request starvation + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByDeadlock")} +``` + +In this example, 10 groups are created but only 2 can be processed concurrently. +Groups 3-10 cannot make progress because the downstream subscriber is busy with groups 1-2. +Meanwhile, groups 1-2 may not complete because they're waiting for backpressure signals from the full pipeline. +The problem is even more exacerbated with infinite streams and infinite groups. + +#### How to avoid request starvation + +1. **Set concurrency >= number of groups**: If you know the maximum number of groups in advance, set the concurrency parameter to at least that number using `.merge(n)` +2. **Use unbounded concurrency**: Call `.merge(Integer.MAX_VALUE)` to allow unlimited number of concurrent groups +3. **Use concatenate instead**: Process groups sequentially (see below) + +### Using concatenate + +With concatenate, groups are processed sequentially - each group must fully terminate before the next group can start processing: + +```java linenums="1" +{=snippet:insert("java/guides/operators/GroupingItemsTest.java", "groupByWithConcatenate")} +``` + +## Choosing between group().by() and split() + +Mutiny provides both `group().by()` and `split()` operators. Here's when to use each: + +- **Use `group().by()`** when you don't know the keys in advance and the number of groups is dynamic. +- **Use `split()`** when you know all possible keys upfront (defined by an enum) and you want individual `Multi` instances for each split. + +See the [splitting guide](multi-split.md) for more details on `split()`. diff --git a/documentation/docs-roq/content/3.3.0/guides/handling-null.md b/documentation/docs-roq/content/3.3.0/guides/handling-null.md new file mode 100644 index 000000000..8a0e7b970 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/handling-null.md @@ -0,0 +1,33 @@ +--- +title: "How to handle null?" +layout: page +tags: [guide, beginner] +--- + +# How to handle null? + +The `Uni` type can emit `null` as item. + +While there are mixed feelings about `null`, it's part of the Java language and so handled in the `Uni` type. + +> [!IMPORTANT] +> `Multi` does not support `null` items as it would break the compatibility with the _Reactive Streams_ protocol. + +Emitting `null` is convenient when returning `Uni`. +However, the downstream must expect `null` as item. + +Thus, `Uni` provides specific methods to handle `null` item. +`uni.onItem().ifNull()` lets you decide what you want to do when the received item is `null`: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code")} +``` + +A symmetric group of methods is also available with `ifNotNull` which let you handle the case where the item is _not null_: + +```java linenums="1" +{=snippet:insert("java/guides/UniNullTest.java", "code-not-null")} +``` + +> [!IMPORTANT] +> While supported, emitting `null` should be avoided except for `Uni`. diff --git a/documentation/docs-roq/content/3.3.0/guides/handling-timeouts.md b/documentation/docs-roq/content/3.3.0/guides/handling-timeouts.md new file mode 100644 index 000000000..3e0d380d4 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/handling-timeouts.md @@ -0,0 +1,50 @@ +--- +title: "How to handle timeouts?" +layout: page +tags: [guide, intermediate] +--- + +# How to handle timeouts? + +Unis are often used to represent asynchronous operations, like making an HTTP call. +So, it's not rare to need to add a timeout or a deadline on this kind of operation. +If we don't get a response (receive an item in the Mutiny lingo) before that deadline, we consider that the operation failed. + +We can then recover from this failure by using a fallback value, retrying, or any other failure handling strategy. + +To configure a timeout use `Uni.ifNoItem().after(Duration)`: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "code")} +``` + +When the deadline is reached, you can do various actions. +First you can simply fail: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail")} +``` + +A `TimeoutException` is propagated in this case. +So you can handle it specifically in the downstream: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-recover")} +``` + +You can also pass a custom exception: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fail-with")} +``` + +Failing and recovering might be inconvenient. +So, you can pass a fallback item or `Uni` directly: + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback")} +``` + +```java linenums="1" +{=snippet:insert("java/guides/UniTimeoutTest.java", "fallback-uni")} +``` diff --git a/documentation/docs-roq/content/3.3.0/guides/hot-streams.md b/documentation/docs-roq/content/3.3.0/guides/hot-streams.md new file mode 100644 index 000000000..dab8595ed --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/hot-streams.md @@ -0,0 +1,28 @@ +--- +title: "Hot streams" +layout: page +tags: [guide, advanced] +--- + +# Hot streams + +In a _cold_ stream, the stream is created when one subscriber subscribes to the stream. +So, if no one subscribes, the actual stream is not created, saving resources (that would be wasted because nobody is interested in the items). + +In a _hot_ stream, the stream exists before subscribers subscribe. +The stream emits items even if no subscribers observe the stream. +If there are no subscribers, the items are just dropped. +Subscribers only get items emitted after their subscription, meaning that any previous items would not be received. + +To create a hot stream, you can use `io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor` that: + +- drops items if no subscribers are present, +- forwards items to the set of observing subscribers. + +```java linenums="1" +{=snippet:insert("java/guides/operators/BroadcastProcessorTest.java", "code")} +``` + +Note that the `BroadcastProcessor` subscribes to the _hot_ source aggressively and without back-pressure. +However, the `BroadcastProcessor` enforces the back-pressure protocol per subscriber. +If a subscriber is not ready to handle an item emitted by the _hot_ source, an `io.smallrye.mutiny.subscription.BackPressureFailure` is forwarded to this subscriber. diff --git a/documentation/docs-roq/content/3.3.0/guides/imperative-to-reactive.md b/documentation/docs-roq/content/3.3.0/guides/imperative-to-reactive.md new file mode 100644 index 000000000..2ade8baf3 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/imperative-to-reactive.md @@ -0,0 +1,70 @@ +--- +title: "From imperative to reactive" +layout: page +tags: [guide, advanced] +--- + +# From imperative to reactive + +If you use Mutiny, there is a good chance you may want to avoid blocking the caller thread. + +In a _pure_ reactive application, the application logic is executed on one of the few I/O threads, and blocking one of these would have dramatic consequences. +So, here is the big question: _how do you deal with blocking code?_ + +Let's imagine you have blocking code (e.g., connecting to a database using JDBC, reading a file from the file system...), and you want to integrate that into your reactive pipelines while avoiding blocking. +You would need to isolate such blocking parts of your code and run these parts on worker threads. + +Mutiny provides two operators to customize the threads used to handle events: + +* `runSubscriptionOn` - to configure the thread used to execute the code happening at subscription-time +* `emitOn` - to configure the thread used to dispatch events downstream + +## Running blocking code on subscription + +It is very usual to deal with the blocking call during the subscription. +In this case, the `runSubscription` operator is what you need: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "uni-runSubscriptionOn")} +``` + +The code above creates a Uni that will supply the item using a blocking call, here the `invokeRemoteServiceUsingBlockingIO` method. +To avoid blocking the subscriber thread, it uses `runSubscriptionOn` which switches the thread and call `invokeRemoteServiceUsingBlockingIO` on another thread. +Here we pass the default worker thread pool, but you can use your own executor. + +> [!TIP] +> +> What's that default worker pool? +> +> In the previous snippet, you may wonder about `Infrastructure.getDefaultWorkerPool()`. +> Mutiny allows the underlying platform to provide a default worker pool. +> `Infrastructure.getDefaultWorkerPool()` provides access to this pool. + + +If the underlying platform does not provide a pool, a default one is used. + +Note that `runSubscriptionOn` does not subscribe to the Uni. +It specifies the executor to use when a subscription happens. + +While the snippet above uses `Uni`, you can also use `runSubscriptionOn` on a `Multi`. + +## Executing blocking calls on event + +Using `runSubscriptionOn` works when the blocking operation happens at subscription time. +But, when dealing with `Multi` and need to execute blocking operations for each item, you need to use `emitOn`. + +While `runSubscriptionOn` runs the subscription on the given executor, `emitOn` configures the executor used to propagate downstream the items, failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ImperativeToReactiveTest.java", "multi-emitOn")} +``` + +`emitOn` is also available on `Uni`. + +> [!WARNING] +> +> Be careful as this operator can lead to concurrency problems with non thread-safe objects such as CDI request-scoped beans. +> It might also break reactive-streams semantics with items being emitted concurrently. + + + diff --git a/documentation/docs-roq/content/3.3.0/guides/integrate-a-non-reactive-source.md b/documentation/docs-roq/content/3.3.0/guides/integrate-a-non-reactive-source.md new file mode 100644 index 000000000..6d297a057 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/integrate-a-non-reactive-source.md @@ -0,0 +1,22 @@ +--- +title: "How can I create a Multi from a non-reactive source?" +layout: page +tags: [guide, advanced] +--- + +# How can I create a Multi from a non-reactive source? + +The `UnicastProcessor` is an implementation of `Multi` that lets you enqueue items in a queue. + +The items are then dispatched to the subscriber using the request protocol. +While this pattern is against the idea of back-pressure, it lets you connect sources of data that do not support back-pressure with your subscriber. + +In the following example, the `UnicastProcessor` is used by a thread emitting items. +These items are enqueued in the processor and replayed when the subscriber is connected, following the request protocol. + +```java linenums="1" +{=snippet:insert("java/guides/operators/UnicastProcessorTest.java", "code")} +``` + +By default, the `UnicastProcessor` uses an unbounded queue. +You can also pass a fixed size queue that would reject the items once full. \ No newline at end of file diff --git a/documentation/docs-roq/content/3.3.0/guides/joining-unis.md b/documentation/docs-roq/content/3.3.0/guides/joining-unis.md new file mode 100644 index 000000000..4b88e1b06 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/joining-unis.md @@ -0,0 +1,65 @@ +--- +title: "Joining several unis" +layout: page +tags: [guide, intermediate] +--- + +# Joining several unis + +A `Uni` represents an operation that either emits a value or a failure. +Examples of operations that fit into a `Uni` include: HTTP client requests, database `insert` queries, sending messages to a broker, etc. + +It is common to trigger several _concurrent_ operations, then _join_ on the results. +For instance you can make HTTP requests to 3 different HTTP APIs, then collect all HTTP responses. +Or you can just take the response from the one who was the fastest. + +`Uni` offers the `join` group to assemble all results from a list of `Uni`, pick the first one that terminates, or pick the first one that terminates with a value. + +## Joining multiple unis + +Given multiple `Uni`, you can join them all and obtain a `Uni` that emits a list of values: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all")} +``` + +The assembled values are in the same order as the list of unis. +The last call to `.andCollectFailures()` specifies that if one or several `Uni` fail, then the failures are assembled in a `CompositeException`. + +Sometimes you just want to _fail fast_ if any of the `Uni` fails, and not wait for all unis to terminate: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-all-ff")} +``` + +When any `Uni` fails, then the failure is directly forwarded as a failure of `res`. + +## Joining on the first Uni + +In some cases you do not want to have all the results but just that of the first `Uni` to respond. +There are actually 2 different cases, depending on whether you want the result of the first `Uni` that emits a value, or just the result of the first `Uni` to terminate. + +If you want to get the first `Uni` that terminates: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first")} +``` + +If you want to have the first `Uni` that emits a value (and forget the first failures), then: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "join-first-withitem")} +``` + +When all unis fail then `res` fails with a `CompositeException` that reports all failures. + +## Using a builder object + +There are situations where it can be more convenient to gather the unis to join in an iterative fashion. +For this purpose you can use a builder object, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/UniJoinTest.java", "builder")} +``` + +The builder offers `joinAll()` and `joinFirst()` methods. diff --git a/documentation/docs-roq/content/3.3.0/guides/kotlin.md b/documentation/docs-roq/content/3.3.0/guides/kotlin.md new file mode 100644 index 000000000..4c86f6f2c --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/kotlin.md @@ -0,0 +1,168 @@ +--- +title: "Kotlin integration" +layout: page +tags: [guide, intermediate] +--- + +# Kotlin integration + +The module `mutiny-kotlin` provides an integration with Kotlin for use with coroutines and convenient language features. + +There are extension methods available for converting between Mutiny and Kotlin (coroutine) types. +For implementation details please have also a look to these methods' documentation. + +## Dependency coordinates + +The coroutine extension functions are shipped in the package `io.smallrye.mutiny.coroutines`. + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "importStatements")} +``` + +You need to add the following dependency to your project: + +#### Maven + +```xml + + io.smallrye.reactive + mutiny-kotlin + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}") +``` + +#### Gradle (Groovy) + +```groovy +implementation "io.smallrye.reactive:mutiny-kotlin:{=cdi:attributes.versions.mutiny}" +``` + +## Awaiting a Uni in coroutines + +Within a coroutine or suspend function you can easily await Uni events in a suspended way: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniAwaitSuspending")} +``` + +## Processing a Multi as Flow + +The coroutine `Flow` type matches `Multi` semantically, even though it isn't a feature complete reactive streams implementation. +You can process a `Multi` as `Flow` as follows: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "multiAsFlow")} +``` + +> [!NOTE] +> +> There's no flow control availabe for Kotlin's `Flow`. Published items are buffered for consumption using a coroutine `Channel`. +> The buffer size and overflow strategy of that `Channel` can be configured using optional arguments: +> `Multi.asFlow(bufferCapacity = Channel.UNLIMITED, bufferOverflowStrategy = BufferOverflow.SUSPEND)`, +> for more details please consult the method documentation. + + +## Providing a Deferred value as Uni + +The other way around is also possible, let a Deferred become a Uni: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "deferredAsUni")} +``` + +## Creating a Multi from a Flow + +Finally, creating a Multi from a Flow is also possible: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "flowAsMulti")} +``` + +## Awaiting Multi results in coroutines + +You can consume `Multi` results directly from suspend functions without converting to `Flow` first: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/MultiAwait.kt", "multiAwaitList")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/MultiAwait.kt", "multiAwaitFirst")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/MultiAwait.kt", "multiAwaitLast")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/MultiAwait.kt", "multiAwaitEach")} +``` + +## Language convenience + +### Unit instead of Void (null) value + +Kotlin has a special value type `Unit` similar to Java's `Void`. +While regular `Uni` holds a `null` item, you can get a `Unit` by using the extension function `replaceWithUnit()`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniReplaceWithUnit")} +``` + +### Uni builder + +Building a `Uni` from Kotlin code can easily be achieved using the following builders available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/UniExt.kt", "uniBuilder")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/Coroutines.kt", "uniBuilder")} +``` + +### Multi builder + +Building a `Multi` from Kotlin code can be achieved using the `multi` builder, available as regular or coroutine variant: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/MultiExt.kt", "multiBuilder")} +``` + +The builder accepts an optional back-pressure strategy: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/MultiExt.kt", "multiBuilderBackPressure")} +``` + +A coroutine variant allows suspend calls within the builder: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/MultiExt.kt", "multiBuilderSuspend")} +``` + +### Tuple destructuring + +Mutiny's `Tuple2` through `Tuple9` support Kotlin destructuring declarations: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/TupleExt.kt", "tupleDestructuring")} +``` + +### Typed failure handling + +Use reified generics for concise failure type matching on both `Uni` and `Multi`: + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/FailureExt.kt", "uniReifiedOnFailure")} +``` + +```kotlin linenums="1" +{=snippet:insert("kotlin/guides/FailureExt.kt", "multiReifiedOnFailure")} +``` diff --git a/documentation/docs-roq/content/3.3.0/guides/logging.md b/documentation/docs-roq/content/3.3.0/guides/logging.md new file mode 100644 index 000000000..eebd1dbaa --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/logging.md @@ -0,0 +1,64 @@ +--- +title: "Logging events" +layout: page +tags: [guide, beginner] +--- + +# Logging events + +Both `Uni` and `Multi` offer a `log` operator that can be used to trace events as they flow through operators. + +Mutiny does not make any assumption on _how_ logging is defined, and does not rely on any specific logging API. + +## Using a logging operator + +The `log` method comes in 2 forms: one that takes an identifier and one that derives the identifier from the upstream class: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "log")} +``` + +Here the `log` operator traces all events between the `onItem().transform(...)` operator and the subscriber, as in the following output: + +``` +11:01:48.709 [main] INFO Multi.MultiMapOp.0 - onSubscription() +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - request(9223372036854775807) +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(10) +>>> 10 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(20) +>>> 20 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onItem(30) +>>> 30 +11:01:48.711 [main] INFO Multi.MultiMapOp.0 - onCompletion() +``` + +There are a few things to note here: + +1. we are logging on a `Multi`, so the logging event is prefixed with `Multi` (and `Uni` in the case of a... `Uni`), and +2. since we did not specify any identifier in the `log` method call, `MultiMapOp` has been derived from the preceding operator (non-qualified) class name, and +3. since there can be multiple subscriptions an integer is appended to the identifier (`0`, `1`, `2`, ...). + +## Defining logging + +What happens when events are being logged is defined with the `Infrastructure` class. +Events are written by default to the standard console output in a format similar to: + +``` +[--> Multi.MultiMapOp.0 | onSubscription() +[--> Multi.MultiMapOp.0 | request(9223372036854775807) +[--> Multi.MultiMapOp.0 | onItem(10) +[--> Multi.MultiMapOp.0 | onItem(20) +[--> Multi.MultiMapOp.0 | onItem(30) +[--> Multi.MultiMapOp.0 | onCompletion() +``` + +The following is an example of configuring logging with http://www.slf4j.org[SLF4J]: + +```java linenums="1" +{=snippet:insert("java/guides/infrastructure/OperatorLoggingTest.java", "set-logger")} +``` + +> [!TIP] +> +> Note that this is only useful to do when embedding Mutiny in your own stack, some frameworks like [Quarkus](https://quarkus.io) will already have defined the correct logging strategy. + diff --git a/documentation/docs-roq/content/3.3.0/guides/merging-and-concatenating-streams.md b/documentation/docs-roq/content/3.3.0/guides/merging-and-concatenating-streams.md new file mode 100644 index 000000000..f5071765c --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/merging-and-concatenating-streams.md @@ -0,0 +1,126 @@ +--- +title: "Merging and Concatenating Streams" +layout: page +tags: [guide, intermediate] +--- + +# Merging and Concatenating Streams + +Merging or concatenating streams is a frequent operation which consists in taking multiple streams and creating a new `Multi` out of them. +Such an operation observes the items emitted by the different streams and produces a new `Multi` emitting the events. + +All the streams merged or concatenated this way should emit the same type of items. + +## The difference between merge and concatenate + +Understanding the difference between _merge_ and _concatenate_ is essential. + +When _merging_ streams, it observes the different upstreams and emits the items as they come. +If the streams emit their items concurrently, the items from the different streams are interleaved. + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant M as Merged stream + + M-->>A: subscribe + M-->>B: subscribe + A-->>M: onSubscribe(s) + + A->>M: onItem(1) + + B-->>M: onSubscribe(s) + + A->>M: onItem(2) + B->>M: onItem(a) + A->>M: onItem(3) + B->>M: onItem(b) + B->>M: onItem(c) +``` + +When using _merge_, failures are also propagated to the merged stream, and no more items are emitted after that failure. +The _completion_ event is only emitted by the merged stream when all the observed streams are completed. + +But if we want to keep the order of the observed stream, we need to _concatenate_. + +When _concatenating_, it waits for the first stream to complete before subscribing to the second one. Thus, it ensures that all the items from the first stream have been emitted before emitting the second stream items. It preserves an order corresponding to the source: + +```mermaid +sequenceDiagram + autonumber + participant A as Stream A + participant B as Stream B + participant C as Concatenated stream + + C-->>A: subscribe + A-->>C: onSubscribe(s) + + A->>C: onItem(1) + A->>C: onItem(2) + A->>C: onItem(3) + + A-->>C: onCompletion() + + C-->>B: subscribe + B-->>C: onSubscribe(s) + + B->>C: onItem(a) + B->>C: onItem(b) + B->>C: onItem(c) +``` + +When the first stream emits the completion event, it switches to the second stream, and so on. +When the last stream completes, the concatenated stream sends the completion event. +As for _merge_, if a stream fails then there won't be further events. + +## Merging Multis + +To create a new `Multi` from the _merge_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge")} +``` + +For example, we can merge multiple streams emitting periodical events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "merge-ticks")} +``` + +and the output would be similar to: + +```text +Got item: Stream 1 - 0 +Got item: Stream 2 - 0 +Got item: Stream 3 - 0 +Got item: Stream 3 - 1 +Got item: Stream 1 - 1 +Got item: Stream 3 - 2 +Got item: Stream 2 - 1 +Got item: Stream 3 - 3 +Got item: Stream 1 - 2 +Got item: Stream 3 - 4 +Got item: Stream 3 - 5 +``` + +## Concatenating Multis + +To create a new `Multi` from the _concatenation_ of multiple `Multi` streams use: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concat")} +``` + +Don't forget that the streams order matters in this case, as `(streamA, streamB)` does not provide the same result as `(streamB, streamA)`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/MergeConcatTest.java", "concatenate-strings")} +``` + +> [!IMPORTANT] +> +> If one of the concatenated streams is unbounded (infinite), the next streams in the list won't be consumed! + + diff --git a/documentation/docs-roq/content/3.3.0/guides/multi-split.md b/documentation/docs-roq/content/3.3.0/guides/multi-split.md new file mode 100644 index 000000000..e64009ffe --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/multi-split.md @@ -0,0 +1,52 @@ +--- +title: "Splitting a Multi into several Multi" +layout: page +tags: [guide, intermediate] +--- + +# Splitting a Multi into several Multi + +It is possible to split a `Multi` into several `Multi` streams. + +## Using the split operator + +Suppose that we have a stream of strings that represent _signals_, and that we want a `Multi` for each kind of signal: + +- `?foo`, `?bar` are _input_ signals, +- `!foo`, `!bar` are _output_ signals, +- `foo`, `bar` are _other_ signals. + +To do that, we need a function that maps each item of the stream to its target stream. +The splitter API needs a Java enumeration to define keys, as in: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "enum")} +``` + +Now we can use the `split` operator that provides a splitter object, and fetch individual `Multi` for each split stream using the `get` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/SplitTest.java", "splits")} +``` + +This prints the following console output: + +``` +output - a +input - b +output - c +output - d +other - 123 +input - e +``` + +## Notes on using splits + +- Items flow when all splits have a subscriber. +- The flow stops when either of the subscribers cancels, or when any subscriber has a no outstanding demand. +- The flow resumes when all splits have a subscriber again, and when all subscribers have outstanding demand. +- Only one subscriber can be active for a given split. Other subscription attempts will receive an error. +- When a subscriber cancels, then a new subscription attempt on its corresponding split can succeed. +- Subscribing to an already completed or errored split results in receiving the terminal signal (`onComplete()` or `onFailure(err)`). +- The upstream `Multi` gets subscribed to when the first split subscription happens, no matter which split it is. +- The first split subscription passes its context, if any, to the upstream `Multi`. It is expected that all split subscribers share the same context object, or the behavior of your code will most likely be incorrect. diff --git a/documentation/docs-roq/content/3.3.0/guides/pagination.md b/documentation/docs-roq/content/3.3.0/guides/pagination.md new file mode 100644 index 000000000..645a32ab5 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/pagination.md @@ -0,0 +1,60 @@ +--- +title: "How to use paginated APIs?" +layout: page +tags: [guide, intermediate] +--- + +# How to use paginated APIs? + +There are many REST / HTTP APIs using pagination, _i.e._ return only a subset of the results and you need to request the next _page_ to get the next batch. +Each batch contains a list of item(s). + +To use this kind of API and generate a continuous stream of items, you need to use the `Multi.createBy().repeating()` function. +However, we need to pass a cursor / state to advance and avoid requesting again and again the same page. +Fortunately, `repeating` provides methods to pass a shared state. +So by combining these methods and `disjoint` you can generate streams from these pages: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code")} +``` + +First, you create a `Multi` containing the items emitted by the `CompletionStage` supplier and pass a state supplier to progress among the pages. + +Then, use `until` to call the paginated API until we have all the items. +At the point we have a stream of list of item such as `["a", "b", "c"], ["d", "e"], []`. +However, we want the following stream: `"a", "b", "c", "d", "e"`. +The `disjoint` method does exactly this. +It gets the items from the lists and passes them downstream: + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Disjoint operator + participant D as Subscriber + + M->>O: onItem([a,b,c]) + O->>D: onItem(a) + O->>D: onItem(b) + O->>D: onItem(c) + M->>O: onItem([d,e]) + O->>D: onItem(d) + O->>D: onItem(e) +``` + +`Multi.createBy().repeating()` lets you choose the number of iterations using: + +- `atMost` - exact number of repetitions (or failure happens before reaching that number) +- `until` - the repetition is stopped if the item emitted by the `Uni` **passes** a test (predicate). + It does not propagate the item that did pass the check, and it stops the repetition. + The check verifies if the current item does not contain valid data. +- `whilst` - the repetition is stopped if the item emitted by the `Uni` **does not pass** a test (predicate). + It does propagate the item downstream even if the check does not pass. + However, it stops the repetition. + The test verifies if there is a _next_ batch to be retrieved. + +The following code illustrates the usage of `whilst`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PaginationTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.3.0/guides/polling.md b/documentation/docs-roq/content/3.3.0/guides/polling.md new file mode 100644 index 000000000..8151ce837 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/polling.md @@ -0,0 +1,22 @@ +--- +title: "How to use polling?" +layout: page +tags: [guide, advanced] +--- + +# How to use polling? + +There are many poll-based API around us. +Sometimes you need to use these APIs to generate a stream from the polled values. + +To do this, use the `repeat()` feature: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code")} +``` + +You can also stop the repetition using the `repeat().until()` method which will continue the repetition until the given predicate returns `true`, and/or directly create a `Multi` using `Multi.createBy().repeating()`: + +```java linenums="1" +{=snippet:insert("java/guides/operators/PollableSourceTest.java", "code2")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.3.0/guides/reactive-to-imperative.md b/documentation/docs-roq/content/3.3.0/guides/reactive-to-imperative.md new file mode 100644 index 000000000..dbd521950 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/reactive-to-imperative.md @@ -0,0 +1,53 @@ +--- +title: "From reactive to imperative" +layout: page +tags: [guide, advanced] +--- + +# From reactive to imperative + +There are use cases where you need the items in an imperative manner instead of asynchronous. +Typically, when you serve an HTTP request from a worker thread, you can block. + +Mutiny provides the ability to block until you get the items. + +## Awaiting on Uni's item + +When dealing with a `Uni,` you can block and await the item using: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "await")} +``` + +This method blocks the caller thread until the observed `uni` emits the item. +Note that the returned item can be `null` if the `uni` emits `null.` +If the `uni` fails, it throws the exception, wrapped in the `CompletionException` for _checked_ exception. + +Blocking forever may not be a great idea. +You can use `uni.await().atMost(Duration)` to pass a deadline. +When the deadline is reached, a `TimeoutException` is thrown: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "atMost")} +``` + +## Iterating over Multi's items + +When dealing with a `Multi,` you may want to iterate over the items using a simple "foreach." +You can achieve this using `multi.subscribe().asIterable()`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "iterable")} +``` + +The returned `iterable` is blocking. +It waits for the next items, and during that time, blocks the caller thread. + +The iteration ends once the last item is consumed. +If the `multi` emits a failure, an exception is thrown. + +Similar to `asIterable()`, the `asStream` method lets you retrieve a `java.util.stream.Stream`: + +```java linenums="1" +{=snippet:insert("java/guides/integration/ReactiveToImperativeTest.java", "stream")} +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.3.0/guides/replaying-multis.md b/documentation/docs-roq/content/3.3.0/guides/replaying-multis.md new file mode 100644 index 000000000..8e0a414c3 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/replaying-multis.md @@ -0,0 +1,91 @@ +--- +title: "Replaying Multis" +layout: page +tags: [guide, advanced] +--- + +# Replaying Multis + +A `Multi` is a _cold-source_: no processing happens until you subscribe. + +While the `broadcast` operator can be used so that multiple subscribers consume a `Multi` events _at the same time_, it does not support replaying items for _late subscribers_: when a subscriber joins after the `Multi` has completed (or failed), then it won't receive any item. + +This is where _replaying_ can be useful. + +## Replaying all events + +Replaying all events from an upstream `Multi` works as follows: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-all")} +``` + +Both `item_1` and `item_2` trigger new subscriptions, and both lists contain the following elements: + +``` +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +Replaying works by turning `upstream` into a _hot-stream_, meaning that it gets requested `Long.MAX_VALUE` elements. +This is done when the first subscription happens. + +The replay operator stores the items in an internal _replay log_, and then each subscriber gets to replay them. + +> [!IMPORTANT] +> +> Subscribers demand and cancellation requests are honored while replaying, but `upstream` cannot be cancelled. +> +> Be careful with unbounded streams as you can exhaust memory! +> +> In such cases or when you need to replay large amounts of data, you might opt to use some eventing middleware rather than Mutiny replays. + + +## Replaying the last 'n' events + +You can limit the number of elements to replay by using the `upTo` method: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-last")} +``` + +Each new subscriber gets to replay the last `n` elements from where the replay log is at subscription time. +For instance the first subscriber can observe all events, while a subscriber that joins 2 seconds later might not observe the earlier events. + +Since `Multi.createFrom().range(0, 10)` is an _immediate_ stream, both `item_1` and `item_2` lists contain the last items: + +``` +[7, 8, 9] +``` + +## Prepending with seed data + +In some cases you might want to prepend some _seed_ data that will be available for replay before the upstream starts emitting. + +You can do so using an `Iterable` to provide such seed data: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-seed")} +``` + +In which case subscribers can observe the following events: + +``` +[-10, -5, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +## Replay of failures and completions + +Subscribers get to observe not just items but also the failure and completion events: + +```java linenums="1" +{=snippet:insert("java/guides/operators/ReplayTest.java", "replay-errors")} +``` + +Running this code yields the following output for any subscriber: + +``` +-> 7 +-> 8 +-> 9 +Failed: boom +``` \ No newline at end of file diff --git a/documentation/docs-roq/content/3.3.0/guides/rx.md b/documentation/docs-roq/content/3.3.0/guides/rx.md new file mode 100644 index 000000000..84658efc6 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/rx.md @@ -0,0 +1,32 @@ +--- +title: "Using map, flatMap and concatMap" +layout: page +tags: [guide, advanced] +--- + +# Using map, flatMap and concatMap + +If you are a seasoned reactive developer, you may miss the `map`, `flatMap`, `concatMap` methods. + +The Mutiny API is quite different from the _standard_ reactive eXtensions API. + +There are multiple reasons for this choice. +Typically, _flatMap_ is not necessarily well understood by every developer, leading to potentially catastrophic consequences. + +That being said, Mutiny provides the _map_, _flatMap_ and _concatMap_ methods, implementing the most common variant for each: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "rx")} +``` + +The Mutiny equivalents are: + +* `map -> onItem().transform()` +* `flatMap -> onItem().transformToUniAndMerge` and `onItem().transformToMultiAndMerge` +* `concatMap -> onItem().transformToUniAndConcatenate` and `onItem().transformToMultiAndConcatenate` + +The following snippet demonstrates how to uses these methods: + +```java linenums="1" +{=snippet:insert("java/guides/RxTest.java", "mutiny")} +``` diff --git a/documentation/docs-roq/content/3.3.0/guides/shortcut-methods.md b/documentation/docs-roq/content/3.3.0/guides/shortcut-methods.md new file mode 100644 index 000000000..c192cadcb --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/shortcut-methods.md @@ -0,0 +1,43 @@ +--- +title: "Shortcut methods" +layout: page +tags: [guide, beginner] +--- + +# Shortcut methods + +The Mutiny API is decomposed around the idea of groups, each group handling a specific event. +However, to avoid verbosity, Mutiny also exposes _shortcuts_ for the most used methods. +Be aware that these shorts, while making the code shorter, may harm the readability and understandability. + +To _peek_ at items, you can use the `invoke` method: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "invoke")} +``` + +`invoke` is a shortcut for `onItem().invoke(...)`. + +Mutiny also provides the `call` method for executing an action returning a `Uni`. +This is useful to execute an asynchronous action without modifying incoming item: + +```java linenums="1" +{=snippet:insert("java/guides/ShortcutsTest.java", "call")} +``` +`call` is a shortcut for `onItem().call(...)`. + +The following table lists the available shortcuts available by the `Uni` class: + +| Shortcut | Equivalent | +|----------------------------------------------------------|--------------------------------------------------------------------------------------| +| `uni.map(x -> y)` | `uni.onItem().transform(x -> y)` | +| `uni.flatMap(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.chain(x -> uni2)` | `uni.onItem().transformToUni(x -> uni2)` | +| `uni.invoke(x -> System.out.println(x))` | `uni.onItem().invoke(x -> System.out.println(x))` | +| `uni.call(x -> uni2)` | `uni.onItem().call(x -> uni2)` | +| `uni.eventually(() -> System.out.println("eventually"))` | `uni.onTermination().invoke(() -> System.out.println("eventually"))` | +| `uni.eventually(() -> uni2)` | `uni.onTermination().call((ignoredItem, ignoredError, ignoredCancellation) -> uni2)` | +| `uni.replaceWith(x)` | `uni.onItem().transform(ignored -> x)` | +| `uni.replaceWith(uni2)` | `uni.onItem().transformToUni(ignored -> uni2)` | +| `uni.replaceIfNullWith(x)` | `uni.onItem().ifNull().continueWith(x)` | + diff --git a/documentation/docs-roq/content/3.3.0/guides/spies.md b/documentation/docs-roq/content/3.3.0/guides/spies.md new file mode 100644 index 000000000..314fdebd1 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/spies.md @@ -0,0 +1,67 @@ +--- +title: "Spying on events" +layout: page +tags: [guide, advanced] +--- + +# Spying on events + +Spies are useful when you need to track which _events_ flow into a `Uni` or a `Multi`. +Spies can track events from groups such as `onItem()`, `onFailure()`, `onSubscribe()`, etc. + +The `io.smallrye.mutiny.helpers.spies.Spy` interface offers factory methods to spy on selected groups, or even on all groups. + +## Spying selected groups + +The following example spies on requests and completion group events: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "selected")} +``` + +The standard output stream shall display the following text: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Completed? true +``` + +The number of requests corresponds to `Long.MAX_VALUE`, and a completion event was sent. + +> [!IMPORTANT] +> +> It is important to note that spies observe and report events for all subscribers, not just one in particular. +> +> You should call the `.reset()` method on a given spy to resets its statistics such as the invocation count. + + +## Spying all groups + +You can take advantage of a _global spy_ if you are interested in all event groups: + +```java linenums="1" +{=snippet:insert("java/guides/SpiesTest.java", "global")} +``` + +Running the snippet above gives the following output: + +``` +1 +2 +3 +Number of requests: 9223372036854775807 +Cancelled? false +Failure? null +Items: [1, 2, 3] +``` + +> [!WARNING] +> +> Tracking `onItem()` events on a `Multi` requires storing all items into a list, which can yield an out-of-memory +> exception with large streams. +> +> In such cases consider using `Spy.onItem(multi, false)` to obtain a spy that does not store items, but that can +> still report data such as the number of received events (see `spy.invocationCount()`). diff --git a/documentation/docs-roq/content/3.3.0/guides/take-skip-items.md b/documentation/docs-roq/content/3.3.0/guides/take-skip-items.md new file mode 100644 index 000000000..01ce41ec3 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/take-skip-items.md @@ -0,0 +1,112 @@ +--- +title: "Take/Skip the first or last items" +layout: page +tags: [guide, beginner] +--- + +# Take/Skip the first or last items + +Multi provides the ability to: + +- only forward items from the beginning of the observed multi, +- only forward the last items (and discard all the other ones), +- skip items from the beginning of the multi, +- skip the last items. + +These actions are available from the `multi.select()` and `multi.skip()` groups, allowing to, respectively, select and skip +items from upstream. + +## Selecting items + +The `multi.select().first` method forwards on the _n_ **first** items from the multi. +It forwards that amount of items and then sends the completion signal. +It also cancels the upstream subscription. + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-first")} +``` + +> [!NOTE] +> +> The `select().first()` method selects only the first item. + + +If the observed multi emits fewer items, it sends the completion event when the upstream completes. + +Similarly, The `multi.select().last` operator forwards on the _n_ **last** items from the multi. +It discards all the items emitted beforehand. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-last")} +``` + +> [!NOTE] +> +> The `select().last()` method selects only the last item. + + +The `multi.select().first(Predicate)` operator forwards the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops forwarding the items downstream. +It also sends the completion event and cancels the upstream subscription. + +Finally, `multi.select().first(Duration)` operator picks the first items emitted during a given period. +Once the passed duration expires, it sends the completion event and cancels the upstream subscription. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "take-for")} +``` + +## Skipping items + +You can also skip items using `multi.skip()`. + +The `multi.skip().first(n)` method skips the _n_ **first** items from the multi. +It forwards all the remaining items and sends the completion event when the upstream multi completes. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-first")} +``` + +If the observed multi emits fewer items, it sends the completion event without emitting any items. + +> [!NOTE] +> +> `skip().last()` drops only the very last item. + + +Similarly, The `multi.skip().last(n)` operator skips on the _n_ **last** items from the multi: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-last")} +``` + +The `multi.skip().first(Predicate)` operator skips the items while the passed predicate returns `true`: + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-while")} +``` + +It calls the predicate for each item. +Once the predicate returns `false`, it stops discarding the items and starts forwarding downstream. + +Finally, `multi.skip().first(Duration)` operator skips the first items for a given period. +Once the passed duration expires, it sends the items emitted after the deadline downstream. +If the observed multi completes before the passed duration, it sends the completion event. + + +```java linenums="1" +{=snippet:insert("java/guides/operators/SelectAndSkipTest.java", "skip-for")} +``` diff --git a/documentation/docs-roq/content/3.3.0/guides/testing.md b/documentation/docs-roq/content/3.3.0/guides/testing.md new file mode 100644 index 000000000..7594d795e --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/testing.md @@ -0,0 +1,67 @@ +--- +title: "How can I write unit / integration tests?" +layout: page +tags: [guide, beginner] +--- + +# How can I write unit / integration tests? + +Mutiny provides subscribers for `Uni` and `Multi` offering helpful assertion methods. +You can use them to test pipelines. + +## Testing a Uni + +Here is an example to test a `Uni`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni")} +``` + +You can also use predicate-based assertions and item inspection: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "uni-predicate")} +``` + +## Testing a Multi + +Testing a `Multi` pipeline is similar: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi")} +``` + +Predicate-based assertions work on `Multi` too: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "multi-predicate")} +``` + +## Declarative verification with AssertMulti + +For multi-item streams, `AssertMulti` provides a declarative step-by-step verifier. +Build a sequence of expectations, then call `verify()`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "assert-multi")} +``` + +You can control demand explicitly for backpressure testing: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "assert-multi-demand")} +``` + +## Testing failures + +The assertions do not just focus on _good_ outcomes, you can also test failures as in: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "failing")} +``` + +With `AssertMulti`: + +```java linenums="1" +{=snippet:insert("java/guides/TestSubscribersTest.java", "assert-multi-failure")} +``` diff --git a/documentation/docs-roq/content/3.3.0/guides/unchecked-exceptions.md b/documentation/docs-roq/content/3.3.0/guides/unchecked-exceptions.md new file mode 100644 index 000000000..3f7c5f339 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/guides/unchecked-exceptions.md @@ -0,0 +1,40 @@ +--- +title: "Dealing with checked exceptions" +layout: page +tags: [guide, intermediate] +--- + +# Dealing with checked exceptions + +When implementing your reactive pipeline, you write lots of functions (`java.util.function.Function`), consumers (`java.util.function.Consumer`), suppliers (`java.util.function.Supplier`) and so on. + +By default, you cannot throw checked exceptions. + +When integrating libraries throwing checked exceptions (like `IOException`) it's not very convenient to add a `try/catch` block and wrap the thrown exception into a runtime exception: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "rethrow")} +``` + +Mutiny provides utilities to avoid having to do this manually. + +If your operation throws a _checked exception_, you can use the [`io.smallrye.mutiny.unchecked.Unchecked`](https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/io/smallrye/mutiny/unchecked/Unchecked.html) wrappers. + +For example, if your synchronous transformation uses a method throwing a checked exception, wrap it using `Unchecked.function`: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "transform")} +``` +You can also wrap consumers such as in: + +```java linenums="1" +{=snippet:insert("java/guides/UncheckedTest.java", "invoke")} +``` + + +> [!TIP] +> +> You can add the following import statement to simplify the usage of the provided methods: +> +> `import static io.smallrye.mutiny.unchecked.Unchecked.*;` + diff --git a/documentation/docs-roq/content/3.3.0/reference/going-reactive-a-few-pitfalls.md b/documentation/docs-roq/content/3.3.0/reference/going-reactive-a-few-pitfalls.md new file mode 100644 index 000000000..1dc29b654 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/reference/going-reactive-a-few-pitfalls.md @@ -0,0 +1,73 @@ +--- +title: "Going reactive: a few pitfalls" +layout: page +tags: [reference, beginner] +--- + +# Going reactive: a few pitfalls + +Don't get us wrong, reactive programming is a fantastic way to write resource-efficient code! + +That being said, reactive programming has a learning curve that should not be taken lightly, and in some cases it is safer to write imperative code that you fully comprehend over reactive code that you don't fully grok. + +We have assembled a few considerations that we think new users should know before they embark into writing complex reactive business logic. + +## Mutiny doesn't auto-magically make your code asynchronous + +This is a common source of confusion for new reactive programmers. +Mutiny itself **does not perform any scheduling work**, except for the [`emitOn` and `runSubscriptionOn` operators](../guides/emit-on-vs-run-subscription-on.md). + +Consider the following code where we _join_ results from multiple asynchronous operations, materialised by the `Uni`-returning `fetch` method: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "noMagicJoin")} +``` + +You might think that the `join` operator schedules the calls to `fetch` to be run concurrently, and then collects the results into a list. +This is not how it works! + +The `join` operator does subscribe to each `Uni` returned by each call to `fetch`. +When it receives a value, it puts it into a list, and when all values have been received, that list is emitted. +The threads involved here are the ones that emit values in `fetch`. +If `fetch` uses async I/O underneath then you should observe true concurrency, but if `fetch` just emits a value right when the subscription happens then you will merely observe a sequential execution of each call to `fetch`, in order. + +## When to prefer `Uni>` over `Multi` + +The reason why `Multi` exists is to model streams over back-pressured sources. +By conforming to the [Reactive Streams protocol](https://www.reactive-streams.org/), a `Multi` respects the control flow requests from its subscribers, avoiding classic problems such as a fast producer and a slow consumer that can yield to memory exhaustion problems. + +That being said, not everything is a stream. +Take the example of relational databases: **databases don't stream!** (for the most parts) + +When you do a query such as `SELECT * FROM ABC WHERE INDEX < 123`, you get result rows. +While you might wrap the results in a `Multi` as a convenience, the network protocol of the database still sends you all `Row` values and is very unlikely to support any notion of back-pressure on a SQL query result. + +This is why `Uni>` is in this case a better representation of an asynchronous operation than `Multi`, because the underlying networked service protocol does not provide you with any back-pressured stream. + +## Creating `Uni` and `Multi` from in-memory data might be suspicious + +You will find lots of occurrences of creating `Uni` and `Multi` from in-memory data in this documentation, as in: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "inMemoryData")} +``` + +This is convenient and expected when creating tests and examples, but this should be a strong warning in production. +Indeed, if we have a method such as the following: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "suspiciousPublisher")} +``` + +then it is clear that there is nothing _"reactive"_ in this code _(sadly, you can find such idioms in some well-known "reactive" client libraries, but we digress)_. + +As a rule of thumb, if your **initial** publisher does not make any I/O operation and it already has the data available in memory, then it is suspicious: + +- if it is a `Uni`, then it does not really model an asynchronous I/O operation because the data is already here, and +- if it is a `Multi` then not only there is no asynchronous I/O operation involved, but there is no need for a back-pressure protocol either (see the previous section). + +What is not suspicious however is to create, say, a `Multi` to perform a transformation operation: + +```java linenums="1" +{=snippet:insert("java/guides/reference/PitfallsTest.java", "flatmap-ism")} +``` diff --git a/documentation/docs-roq/content/3.3.0/reference/migrating-to-mutiny-2.md b/documentation/docs-roq/content/3.3.0/reference/migrating-to-mutiny-2.md new file mode 100644 index 000000000..739589661 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/reference/migrating-to-mutiny-2.md @@ -0,0 +1,55 @@ +--- +title: "Migrating to Mutiny 2" +layout: page +tags: [reference, beginner] +--- + +# Migrating to Mutiny 2 + +The upgrade is transparent for most code bases that _use_ Mutiny in applications (e.g., Quarkus applications). + +## Highlights + +- Mutiny 2 is a major release with source and binary incompatible changes to the Mutiny `0.x` and `1.x` series. +- The main highlight of Mutiny 2 is that it is now based on top of the `java.util.concurrent.Flow` APIs instead of the legacy _Reactive Streams APIs_. +- The `Flow` APIs have been part of the JDK since Java 9, and they are the modern _Reactive Streams APIs_. +- Mutiny remains a faithful implementation of the _Reactive Streams_ specification and passes the `Flow` variant of the _Reactive Streams TCK_. +- Deprecated APIs in Mutiny `1.x` have been removed, and experimental APIs have been promoted. + +## Impact of the switch from legacy Reactive Streams APIs to JDK Flow + +- The `Flow` types are isomorphic to the legacy _Reactive Streams API_ types. +- We recommend that you migrate to `Flow` in your own code bases. +- You should encourage third-party libraries to migrate to `Flow`. +- You can always use _adapters_ to go back and forth between `Flow` and legacy _Reactive Streams_ types. + +### General guidelines + +- If your code _only uses_ `Uni` and `Multi` (i.e., not `org.reactivestreams.Publisher`), then you will be source-compatible with Mutiny 2. You should still recompile and check that your test suites pass. +- If you expose `Multi` as a `org.reactivestreams.Publisher` then you will either need an _adapter_ (see below) or migrate to `java.util.concurrent.Flow.Publisher`. +- If you interact with `org.reactivestreams.Publisher` publishers and you can't migrate them to `java.util.concurrent.Flow.Publisher` (e.g., because it is a third-party library), then you will need an _adapter_. Please encourage third-party libraries to migrate to `Flow`. + +### Adapters between Flow and legacy Reactive Streams APIs + +- We recommend using the adapters from the [Mutiny Zero project](https://smallrye.io/smallrye-mutiny-zero). + - The Maven coordinates are `groupId: io.smallrye.reactive`, `artifactId: mutiny-zero-flow-adapters` + - Use `AdaptersToFlow` to convert from _Reactive Streams_ types to `Flow` types, and + - Use `AdaptersToReactiveStreams` to convert `Flow` types to _Reactive Streams_ types. +- The Mutiny Zero adapters have virtually zero overhead. + +## Other API changes + +### Deprecated API removals + +- `Uni` and `Multi` `onSubscribe()` group is now `onSubscription()`. +- `AssertSubscriber.await()` has been replaced by event-specific methods (items, failure, completion, etc). +- The _RxJava 2_ integration module has been discarded (only RxJava 3 is now supported). + +### Experimental API promotions + +- `Uni` and `Multi` subscription-bound contexts. +- `Uni.join()` publisher. +- `.ifNoItem()` timeout operators. +- `Uni` and `Multi` spies. +- `capDemandsUsing()` and `paceDemand()` request management operators. +- `Multi` `replay()` operator. diff --git a/documentation/docs-roq/content/3.3.0/reference/publications.md b/documentation/docs-roq/content/3.3.0/reference/publications.md new file mode 100644 index 000000000..2c7f4d081 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/reference/publications.md @@ -0,0 +1,19 @@ +--- +title: "Publications" +layout: page +tags: [reference, advanced] +--- + +# Publications + +Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. +**[Analysing the Performance and Costs of Reactive Programming Libraries in Java](https://doi.org/10.1145/3486605.3486788).** +_In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS '21)_, October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. +[(PDF)](https://hal.inria.fr/hal-03409277/document) + +> Modern services running in cloud and edge environments need to be resource-efficient to increase deployment density and reduce operating costs. +> Asynchronous I/O combined with asynchronous programming provides a solid technical foundation to reach these goals. +> Reactive programming and reactive streams are gaining traction in the Java ecosystem. +> However, reactive streams implementations tend to be complex to work with and maintain. +> This paper discusses the performance of the three major reactive streams compliant libraries used in Java applications: RxJava, Project Reactor, and SmallRye Mutiny. +> As we will show, advanced optimization techniques such as operator fusion do not yield better performance on realistic I/O-bound workloads, and they significantly increase development and maintenance costs. diff --git a/documentation/docs-roq/content/3.3.0/reference/uni-and-multi.md b/documentation/docs-roq/content/3.3.0/reference/uni-and-multi.md new file mode 100644 index 000000000..b7ee44ab2 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/reference/uni-and-multi.md @@ -0,0 +1,42 @@ +--- +title: "Uni and Multi" +layout: page +tags: [reference, beginner] +--- + +# Uni and Multi + +Mutiny defines two _reactive_ types: + +* `Multi` - represents streams of _0..*_ items (potentially unbounded) +* `Uni` - represents streams receiving either an item or a failure + +> [!TIP] +> The Mutiny name comes from the contraction of `Multi` and `Uni` names + +Both `Uni` and `Multi` are asynchronous types. +They receive and fire events at any time. + +You may wonder why we make the distinction between `Uni` and `Multi.` +Conceptually, a `Uni` is a `Multi,` right? + +In practice, you don't use `Unis` and `Multis` the same way. +The use cases and operations are different. + +* `Uni` does not need the complete ceremony presented above as the _request_ does not make sense. +* The `subscribe` event expresses the interest and triggers the computation, no need for an additional _request_. +* `Uni` can handle items having a `null` value (and has specific methods to handle this case). +* `Multi` does not allow it (because the Reactive Streams specification forbids it). +* Having a `Uni` implementing `Publisher` would be a bit like having `Optional` implementing `Iterable`. + +In other words, `Uni`: + +* can receive at most 1 `item` event, or a `failure` event +* cannot receive a `completion` event (`null` in the case of 0 items) +* cannot receive a `request` event + +The following snippet shows how you can use `Uni` and `Multi`: + +```java linenums="1" +{=snippet:insert("java/guides/UniMultiComparisonTest.java", "code")} +``` diff --git a/documentation/docs-roq/content/3.3.0/reference/what-is-reactive-programming.md b/documentation/docs-roq/content/3.3.0/reference/what-is-reactive-programming.md new file mode 100644 index 000000000..0666c0d0d --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/reference/what-is-reactive-programming.md @@ -0,0 +1,48 @@ +--- +title: "What is Reactive Programming?" +layout: page +tags: [reference, beginner] +--- + +# What is Reactive Programming? + +Mutiny is a reactive programming library. +If you look on Wikipedia for reactive programming, you will find the following definition: + +> Reactive Programming combines functional programming, the observer pattern, and the iterable pattern. + +While correct, we never found this definition very helpful. +It does not convey clearly what's reactive programming is all about. +So, let's make another definition, much more straightforward: + +> Reactive programming is about programming with data streams. + +That's it. +Reactive programming is about streams and especially, observing them. +It pushes that idea to its limit: with reactive programming, everything is a data stream. + +With reactive programming, you observe streams and implement side effects when _something_ flows in the stream: + +```mermaid +sequenceDiagram + participant S1 as Stream + participant O1 as Observer + + participant S2 as Stream + participant O2 as Observer + + S1->>O1: onItem("a") + S2->>O2: onItem("a") + + S1->>O1: onItem("b") + S2->>O2: onItem("b") + + S2->>O2: onItem("c") + + S1-XO1: onFailure(err) + S2->>O2: onCompletion() +``` + +It's asynchronous by nature as you don't know when the _data_ is going to be seen. +Yet, reactive programming goes beyond this. +It provides a toolbox to compose streams and process events. diff --git a/documentation/docs-roq/content/3.3.0/reference/what-makes-mutiny-different.md b/documentation/docs-roq/content/3.3.0/reference/what-makes-mutiny-different.md new file mode 100644 index 000000000..7bd229c7c --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/reference/what-makes-mutiny-different.md @@ -0,0 +1,142 @@ +--- +title: "What makes Mutiny different?" +layout: page +tags: [reference, beginner] +--- + +# What makes Mutiny different? + +There are other reactive programming libraries out there. +In the Java world, we can mention Project Reactor and Rx Java. + +So, what makes Mutiny different from these two well-known libraries? +**The API!** + +Asynchronous is hard to grasp for most developers, and for good reasons. +Thus, the API must not require advanced knowledge or add cognitive overload. +It should help you design your logic and still be intelligible when you return to the code 6 months later. + +To achieve this, Mutiny is built on three pillars: + +- **Event-Driven** - with Mutiny, you listen for _events_ and handle them, +- **API Navigability** - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle, +- **Simplicity** - Mutiny provides only two types (`Multi` and `Uni`), which can handle any kind of asynchronous interactions. + +## Events? + +When you use Mutiny, you design a pipeline in which the events flow. +Your code observes these events and react. + +Each processing stage is a new pipe you append to the pipeline. +This pipe may change the events, create new ones, drops, buffers, whatever you need. + +In general, events flow from upstream to downstream, from source to sinks. +Some events can _swim_ upstream from the sinks to the source. + +Events going from upstream to downstream are published by `Publishers` and consumed by (downstream) `Subscribers,` which may also produce events for their own downstream, as illustrated by the following diagram: + +```mermaid +sequenceDiagram + participant P as Publisher + participant O1 as Processor 1 + participant O2 as Processor 2 + participant S as Subscriber + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onItem + O1->>O2: onItem + O2->>S: onItem + + P->>O1: onCompletion + O1->>O2: onCompletion + O2->>S: onCompletion +``` + +Four types of events can flow in this direction: + +- **Subscribed** - indicates that the upstream has taken into account the subscription - more on this later, +- **Items** - events containing some (business) _value_, +- **Completion** - event indicating that the source won't emit any more items, +- **Failure** - event telling that something terrible happened upstream and that the source cannot continue to emit items. + +`Failure` and `Completion` are terminal events. +Once they are sent, no more items will flow. + +Three types of events flow in the opposite direction, _i.e._ from downstream to upstream: + +- **Subscription** - event sent by a _subscriber_ to indicate its interest for the events (such as items) emitted by upstream +- **Requests** - event sent by a _subscriber_ indicating how many items event it can handle - this is related to back-pressure +- **Cancellation** - event sent by a _subscriber_ to stop the reception of events. + +In a typical scenario: + +1. A subscriber _subscribes_ to the upstream - the upstream receive the `subscription request`, and when initialized sends the `subscribed` event to the subscriber +2. The subscriber gets the `subscribed` event with a _subscription_ used to emit the `requests` and `cancellation` events +3. The subscriber sends a `request` event indicating how many items it can handle at this moment; it can request 1, _n_, or infinite. +4. The publisher receiving the `request` event starts emitting at most _n_ item events to the subscriber +5. The subscriber can decide at any time to request more events or to cancel the subscription + +```mermaid +sequenceDiagram + participant P as Publisher + participant O as Processor + participant S as Subscriber + + S->>O: subscribe + O->>P: subscribe + + P->>O: subscription + O->>S: subscription + + S->>O: request(5) + O->>P: request(5) + + P->>O: onItem("a") + O->>S: onItem("A") + + P->>O: onItem("b") + O->>S: onItem("B") + + S->>O: cancel() + O->>P: cancel() + +``` + +The `request` event is the cornerstone of the back-pressure protocol. +A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received. + +> [!NOTE] +> Mutiny uses the [Reactive Streams](https://www.reactive-streams.org/) protocol for back-pressure management and the [Java Flow APIs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Flow.html). + +> [!IMPORTANT] +> Don't forget to subscribe! +> +> If no subscriber _subscribes_, no items will be emitted. +> More importantly, nothing will ever happen. +> +> If your program does not do anything, check that it subscribes, it's a very common error. + +## An event-driven API? + +Mutiny is an event-driven API. + +For each type of event, there is an `on` associated method that lets you handle this specific event. +For example: + +```java linenums="1" +{=snippet:insert("java/guides/EventsTest.java", "code")} +``` + +Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what's going on. + +You can see a common pattern emerging: + +```java +.onEvent().invoke(event -> ...); +``` + +`invoke` is just one of the methods available. +Each _group_ proposes methods specific to the type of event. For example, `onFailure().recover`, `onCompletion().continueWith` and so on. diff --git a/documentation/docs-roq/content/3.3.0/reference/why-is-asynchronous-important.md b/documentation/docs-roq/content/3.3.0/reference/why-is-asynchronous-important.md new file mode 100644 index 000000000..2de5a7358 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/reference/why-is-asynchronous-important.md @@ -0,0 +1,47 @@ +--- +title: "Why is asynchronous important?" +layout: page +tags: [reference, beginner] +--- + +# Why is asynchronous important? + +We are living in a distributed world. + +Most of the applications built nowadays are distributed systems. +The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications. + +Still, developing distributed systems is hard! + +![Distributed systems are asynchronous](/images/distributed_systems_are_asynchronous.png) + +Communications in distributed systems are inherently asynchronous and unreliable. +Anything can go wrong, anytime, and often with no prior notice. + +Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems. + +_Correctly_ building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development. + +Most classic applications use a synchronous development model. +Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. +This cost emerges when building I/O intensive applications, quite common in distributed applications. + +In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. +When the request processing needs to interact over the network, it uses that _worker_ thread, which blocks the thread until the response has been received. +This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. +And, to handle more requests concurrently, you need to create more threads. + +Threads come at a cost. +Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. +Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills. + +Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. +While applications using non-blocking I/O are more efficient and better suited for the Cloud's distributed nature, they come with a considerable constraint: you must never block the I/O thread. +Thus, you need to implement your business logic using an asynchronous development model. + +I/O is not the only reason why asynchronous is essential in Today's systems. +Most of the interactions in the real world are asynchronous and event-driven. +Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application. + +Asynchronous is a significant shift. +Mutiny helps you to take the plunge. diff --git a/documentation/docs-roq/content/3.3.0/tags-index.md b/documentation/docs-roq/content/3.3.0/tags-index.md new file mode 100644 index 000000000..2c533c527 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/tags-index.md @@ -0,0 +1,26 @@ +--- +title: Tags Index +layout: page +--- + +# Index + +## Document kinds + +| Tag | Description | +|-------------|------------------------------------------------------------| +| `tutorial` | Tutorials to get started with Mutiny. | +| `guides` | Topic-centric guides on how to use Mutiny. | +| `reference` | Reference documents to understand core concepts in Mutiny. | + +## Audience level + +| Tag | Description | +|----------------|----------------------------------------------------| +| `beginner` | Reading materials if you are new to Mutiny | +| `intermediate` | Intermediate operations beyond the basics | +| `advanced` | Advanced operations beyond classic usage of Mutiny | + +## Tags + +Tags are auto-generated by the Roq tagging plugin. See the individual pages for their associated tags. diff --git a/documentation/docs-roq/content/3.3.0/tutorials/creating-multi-pipelines.md b/documentation/docs-roq/content/3.3.0/tutorials/creating-multi-pipelines.md new file mode 100644 index 000000000..b8f8863f4 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/tutorials/creating-multi-pipelines.md @@ -0,0 +1,142 @@ +--- +title: "Creating `Multi` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Multi` pipelines + +A `Multi` represents a _stream_ of data. +A stream can emit 0, 1, n, or an infinite number of items. + +You will rarely create instances of `Multi` yourself but instead use a reactive client that exposes a Mutiny API. +Still, just like `Uni` there exists a rich API for creating `Multi` objects. + +## The Multi type + +A `Multi` is a data stream that: + +- emits `0..n` item events +- emits a failure event +- emits a completion event for bounded streams + +> [!WARNING] +> Failures are terminal events: after having received a failure no further item will be emitted. + +`Multi` provides many operators that create, transform, and orchestrate `Multi` sequences. +The operators can be used to define a processing pipeline. +The events flow in this pipeline, and each operator can process or transform the events. + +`Multis` are lazy by nature. +To trigger the computation, you must subscribe. + +The following snippet provides a simple example of pipeline using `Multi`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "pipeline")} +``` + +## Subscribing to a Multi + +Remember, if you don't subscribe, nothing is going to happen. +Also, the pipeline is materialized for each _subscription_. + +When subscribing to a `Multi,` you can pass an item callback (invoked when the item is emitted), or pass two callbacks, one receiving the item and one receiving the failure, or three callbacks to handle respectively the item, failure and completion events. + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the stream if need be. + +## Creating Multi from items + +There are many ways to create `Multi` instances. +See `Multi.createFrom()` to see all the possibilities. + +For instance, you can create a `Multi` from known items or from an `Iterable`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "simple")} +``` + +Every subscriber receives the same set of items (`1`, `2`... `5`) just after the subscription. + +You can also use `Suppliers`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber, so each of them will get different values. + +> [!TIP] +> You can create ranges using `Multi.createFrom().range(start, end)`. + +## Creating failing Multis + +Streams can also fail. + +Failures are used to indicate to the downstream subscribers that the source encountered a terrible error and cannot continue emitting items. +Create failed `Multi` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "failed")} +``` + +## Creating empty Multis + +Unlike `Uni,` `Multi` streams don't send `null` items (this is forbidden in _reactive streams_). + +Instead `Multi` streams send completion events indicating that there are no more items to consume. +Of course, the completion event can happen even if there are no items, creating an empty stream. + +You can create such a stream using: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "empty")} +``` + +## Creating Multis using an emitter (_advanced_) + +You can create a `Multi` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Multis from _ticks_ (_advanced_) + +You can create a stream that emit a _ticks_ periodically: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "ticks")} +``` + +The downstream receives a `long,` which is a counter. +For the first tick, it's 0, then 1, then 2, and so on. + +## Creating Multis from a generator (_advanced_) + +You can create a stream from some _initial state_, and a _generator function_: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingMultiTest.java", "generator")} +``` + +The initial state is given through a supplier (here `() -> 1`). +The generator function accepts 2 arguments: + +- the current state, +- an emitter that can emit a new item, emit a failure, or emit a completion. + +The generator function return value is the next _current state_. +Running the previous example gives the following number suite: `\{2, 4, 7, 11, 17, 26, 40, 61}`. + + diff --git a/documentation/docs-roq/content/3.3.0/tutorials/creating-uni-pipelines.md b/documentation/docs-roq/content/3.3.0/tutorials/creating-uni-pipelines.md new file mode 100644 index 000000000..070c95569 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/tutorials/creating-uni-pipelines.md @@ -0,0 +1,119 @@ +--- +title: "Creating `Uni` pipelines" +layout: page +tags: +- tutorial +- beginner +--- + +# Creating `Uni` pipelines + +A `Uni` represents a _stream_ that can only emit either an item or a failure event. + +You rarely create instances of `Uni` yourself, but, instead, use a reactive client exposing a Mutiny API that provides `Uni` objects. +That being said, it can be handy at times. + +## The Uni type + +A `Uni` is a specialized stream that emits only an item or a failure. +Typically, `Uni` are great to represent asynchronous actions such as a remote procedure call, an HTTP request, or an operation producing a single result. + +`Uni` provides many operators that create, transform, and orchestrate `Uni` sequences. + +As said, `Uni` emits either an item or a failure. +Note that the item can be `null,` and the `Uni` API has specific methods for this case. + +Typically, a `Uni` always emits `null` as item event or a failure if the represented operation fails. +You can consider the item event as a completion signal indicating the success of the operation. + +The offered operators can be used to define a processing pipeline. +The event, either the item or failure, flows in this pipeline, and each operator can process or transform the event. +`Unis` are lazy by nature. + +To trigger the computation, you must have a final subscriber indicating your interest. +The following snippet provides a simple example of pipeline using `Uni`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "pipeline")} +``` + +## Subscribing to a Uni + +> [!IMPORTANT] +> Remember: if you don't subscribe, nothing is going to happen. +> What's more, the pipeline is materialized for each _subscription_. + +When subscribing to a `Uni`, you can pass an item callback (invoked when the item is emitted), or two callbacks (one receiving the item and one receiving the failure): + + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "subscription")} +``` + +Note the returned `Cancellable`: this object allows canceling the operation if need be. + +## Creating Unis from items + +There are many ways to create `Uni` instances. +Use `Uni.createFrom()` to see all the possibilities. + +You can, for instance, create a `Uni` from a known value: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "simple")} +``` + +Every subscriber receives the item `1` just after the subscription. + +You can also pass a `Supplier`: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "supplier")} +``` + +The `Supplier` is called for every subscriber. +So, each of them will get a different value. + +## Creating failing Unis + +Operations represented by `Unis` can also emit a failure event, indicating that the operation failed. + +You can create failed `Uni` instances with: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "failed")} +``` + +## Creating `Uni` + +When the represented operation to not produce a result, you still need a way to indicate the operation's completion. +For this, you need to emit a `null` item: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "null")} +``` + +## Creating Unis using an emitter (_advanced_) + +You can create a `Uni` using an emitter. +This approach is useful when integrating callback-based APIs: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "emitter")} +``` + +The emitter can also send a failure. +It can also get notified of cancellation to, for example, stop the work in progress. + +## Creating Unis from a CompletionStage (_advanced_) + +You can also `Uni` objects from `CompletionStage` / `CompletableFuture`. +This is useful when integrating with APIs that are based on these types: + +```java linenums="1" +{=snippet:insert("java/tutorials/CreatingUniTest.java", "cs")} +``` + +> [!TIP] +> You can also create a `CompletionStage` from a `Uni` using `uni.subscribe().asCompletionStage()` + diff --git a/documentation/docs-roq/content/3.3.0/tutorials/getting-mutiny.md b/documentation/docs-roq/content/3.3.0/tutorials/getting-mutiny.md new file mode 100644 index 000000000..44fc5f3ba --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/tutorials/getting-mutiny.md @@ -0,0 +1,95 @@ +--- +title: "Getting started with Mutiny" +layout: page +tags: +- tutorial +- beginner +--- + +# Getting started with Mutiny + +## Using Mutiny in a Java application + +Add the _dependency_ to your project using your preferred build tool: + +#### Apache Maven + +```xml + + io.smallrye.reactive + mutiny + {=cdi:attributes.versions.mutiny} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:mutiny:{=cdi:attributes.versions.mutiny} +``` + +## Using Mutiny with Quarkus + +Most of the [Quarkus](https://quarkus.io) extensions with reactive capabilities already depend on Mutiny. + +You can also add the `quarkus-mutiny` dependency explicitly from the command-line: + +```bash +mvn quarkus:add-extension -Dextensions=mutiny +``` + +or by editing the `pom.xml` file and adding: + +```xml + + io.quarkus + quarkus-mutiny + +``` + +## Using Mutiny with Vert.x + +Most of the [Eclipse Vert.x](https://vertx.io) stack modules are available through the [SmallRye Mutiny Vert.x Bindings](https://smallrye.io/smallrye-mutiny-vertx-bindings/) project. + +Bindings for Vert.x modules are named by prepending `smallrye-mutiny-`. +As an example here's how to add a dependency to the `vertx-core` Mutiny bindings: + +#### Apache Maven + +```xml + + io.smallrye.reactive + smallrye-mutiny-vertx-core + {=cdi:attributes.versions.vertxBindings} + +``` + +#### Gradle (Groovy) + +```groovy +implementation 'io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}' +``` + +#### Gradle (Kotlin) + +```kotlin +implementation("io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings}") +``` + +#### JBang + +```java +//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-core:{=cdi:attributes.versions.vertxBindings} +``` diff --git a/documentation/docs-roq/content/3.3.0/tutorials/handling-failures.md b/documentation/docs-roq/content/3.3.0/tutorials/handling-failures.md new file mode 100644 index 000000000..45199af5c --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/tutorials/handling-failures.md @@ -0,0 +1,88 @@ +--- +title: "Handling failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Handling failures + +Mutiny provides several operators to handle failures. + +Remember, failures are terminal events sent by the observed stream, indicating that something _bad_ happened. +After a failure, no more items are being received. + +When such an event is received, you can: + +- propagate the failure downstream (default), or +- transform the failure into another failure, or +- recover from it by switching to another stream, passing a fallback item, or completing, or +- retrying (covered in the next guide) + +If you don't handle the failure event, it is propagated downstream until a stage handles the failure or reaches the final subscriber. + +> [!IMPORTANT] +> on `Multi`, a failure cancels the subscription, meaning you will not receive any more items. +> The `retry` operator lets you re-subscribe and continue the reception. + +## Observing failures + +It can be useful to execute some custom action when a failure happens. +For example, you can log the failure: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "invoke")} +``` + +> [!TIP] +> You can also execute an asynchronous action using `onFailure().call(Function)`. +> The received failure will be propagated downstream when the `Uni` produced by the passed function emits its item. + +## Transforming failures + +Another useful action on failure is to transform the failure into a _more meaningful_ failure. + +Typically, you can wrap a low-level failure (like an `IOException`) into a business failure (`ServiceUnavailableException`): + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "transform")} +``` + +## Recovering using fallback item(s) + +In general, upon failure, you want to recover. +The first approach is to recover by replacing the failure with an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-item")} +``` + +The second approach receives a `Supplier` to compute the fallback item. +For the downstream, it didn't fail; it gets the fallback item instead. + +However, don't forget that failures are terminal! +So for `Multi`, the downstream receives the fallback item followed by the completion signal, as no more items can be produced. + +## Completing on failure + +When observing a `Multi` you can replace the failure with the completion signal: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-completion")} +``` + +The downstream won't see the failure, just the completion event. + +## Switching to another stream + +On failure, you may want to switch to an alternate stream. +When the failure is received, it subscribes to this other stream and propagates the items from this stream instead: + +```java linenums="1" +{=snippet:insert("java/tutorials/HandlingFailuresTest.java", "recover-switch")} +``` + +The `recoverWithUni` and `recoverWithMulti` methods replace the failed upstream with the returned stream. + +The fallback streams must produce the same type of event as the original upstream. diff --git a/documentation/docs-roq/content/3.3.0/tutorials/hello-mutiny.md b/documentation/docs-roq/content/3.3.0/tutorials/hello-mutiny.md new file mode 100644 index 000000000..844ab137a --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/tutorials/hello-mutiny.md @@ -0,0 +1,70 @@ +--- +title: "Hello Mutiny!" +layout: page +tags: +- tutorial +- beginner +--- + +# Hello Mutiny! + +Once you made Mutiny available to your classpath, you can start writing code. +Let's start with this simple program: + + +```java linenums="1" +{=snippet:insert("java/FirstProgram.java")} +``` + +This program prints: + +``` +>> HELLO MUTINY +``` + +## Dissecting the pipeline + +What's interesting is how this message is _built_. +We described a processing pipeline taking an item, processing it and finally consuming it. + +First, we create a `Uni`, one of the two types with `Multi` that Mutiny provides. +A `Uni` is a stream emitting either a single item or a failure. + +Here, we create a `Uni` emitting the `"hello"` item. +This is the input of our pipeline. +Then we process this item: + +- we append `" mutiny"`, then +- we make it an uppercase string. + +This forms the processing part of our pipeline, and then we finally **subscribe** to the pipeline. + +This last part is essential. +If you don't have a final subscriber, nothing is going to happen. +Mutiny types are lazy, meaning that you need to express your interest. +If you don't, the computation won't even start. + +> [!IMPORTANT] +> If your program doesn't do anything, verify that you didn't forget to subscribe! + +## Mutiny uses a builder API! + +Another important aspect is the pipeline construction. +Appending a new _stage_ to a pipeline returns a new `Uni.` + +The previous program is equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni")} +``` + +It is fundamental to understand that this program is not equivalent to: + +```java linenums="1" +{=snippet:insert("java/FirstProgramTest.java", "uni2")} +``` + +This program just prints `">> hello"`, as it does not use the appended stages and the final subscriber consumes the first `Uni.` + +> [!WARNING] +> Mutiny APIs are not fluent and each computation stage returns a new object. diff --git a/documentation/docs-roq/content/3.3.0/tutorials/mutiny-workshop.md b/documentation/docs-roq/content/3.3.0/tutorials/mutiny-workshop.md new file mode 100644 index 000000000..0af886af8 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/tutorials/mutiny-workshop.md @@ -0,0 +1,19 @@ +--- +title: "Go further with the Mutiny workshop!" +layout: page +tags: +- tutorial +- beginner +--- + +# Go further with the Mutiny workshop! + +One great option to teach yourself Mutiny is to go through the [Mutiny workshop examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples). + +These self-contained [JBang](https://jbang.dev/) scripts cover the main parts of the Mutiny APIs. + +It's a fun and easy way to discover Mutiny! + +Check out [https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples](https://github.com/smallrye/smallrye-mutiny/tree/main/workshop-examples) to learn more. + +![Running a workshop sample](/images/running-workshop-sample.png) diff --git a/documentation/docs-roq/content/3.3.0/tutorials/observing-events.md b/documentation/docs-roq/content/3.3.0/tutorials/observing-events.md new file mode 100644 index 000000000..c091b6567 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/tutorials/observing-events.md @@ -0,0 +1,148 @@ +--- +title: "Observing events" +layout: page +tags: +- tutorial +- beginner +--- + +# Observing events + +Learn how to observe the events emitted by `Uni` and `Multi` instances. + +## Events + +`Uni` and `Multi` emit _events_. +Your code is going to observe and process these events. + +Most of the time, your code is only interested in item and failure events. +But there are other kinds of events such as cancellation, request, completion, and so on: + +| Event | Uni / Multi | Direction | Note | +|------------------:|---------------|:------------------------|:--------------------------------------------------------------------------------------------------------| +| **item** | Uni + Multi | upstream -> downstream | The upstream sent an item. | +| **failure** | Uni + Multi | upstream -> downstream | The upstream failed. | +| **completion** | Multi | upstream -> downstream | The upstream completed. | +| **subscribe** | Uni and Multi | downstream -> upstream | A downstream subscriber is interested in the data. | +| **subscription** | Uni and Multi | upstream -> downstream | Event happening after a `subscribe` event to indicate that the upstream acknowledged the subscription. | +| **cancellation** | Uni and Multi | downstream -> upstream | A downstream subscriber does not want any more events. | +| **overflow** | Multi | upstream -> downstream | The upstream has emitted more than the downstream can handle. | +| **request** | Multi | downstream -> upstream | The downstream indicates its capacity to handle `n` items. | + + +It’s not rare that you need to look at these various events to understand better what’s going on or implement specific side effects. +For example, you may need to close a resource after a completion event or log a message on failure or cancellation. + +For each kind of event, there is an associated group providing the methods to handle that specific event: `onItem()`, `onFailure()`, `onCompletion()` and so on. +These groups provide two methods to _peek_ at the various events without impacting its distribution: `invoke(...)` and `call(...)`. +It does not transform the received event; it notifies you that something happened and let you react. +Once this _reaction_ completes, the event is propagated downstream or upstream depending on the direction of the event. + +## The `invoke` method + +The invoke method is synchronous and the passed callback does not return anything. +Mutiny invokes the configured callback when the observed stream dispatches the event: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke")} +``` + +As said above, `invoke` is synchronous. +Mutiny invokes the callback and propagates the event downstream when the callback returns. +It blocks the dispatching. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().invoke(...) + participant D as Downstream + + M->>O: item1 + O->>D: item1 + + M->>O: item2 + O->>D: item2 + + M->>O: item3 + Note over O: callback execution + O->>D: item3 +``` + +Of course, we highly recommend you not to block. + +The following snippets show how you can log the different types of events. + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "invoke-all")} +``` + +The arrows from the previous code snippet indicate if the event comes from the upstream (source) or downstream (consumer) (see the table above for more details). +The `invoke` method does not change the event, except in one case. +If the callback throws an exception, the downstream does not get the actual event but get a failure event instead. + +When observing the failure event, if the callback throws an exception, Mutiny propagates a `CompositeException` aggregating the original failure and the callback failure. + +## The `call` method + +Unlike `invoke`, `call` is asynchronous, and the callback returns a `Uni` object. + +`call` is often used when you need to implement asynchronous side-effects, such as closing resources. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as onItem().call(...) + participant U as Returned Unis + participant D as Downstream + + M->>O: item1 + O--)U: item1 + U--)O: result1 + O->>D: result1 + + M->>O: item2 + O--)U: item2 + U--)O: result2 + O->>D: result2 + + M->>O: item3 + O--)U: item3 + U--)O: result3 + O->>D: result3 +``` + +Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "call")} +``` + +As shown in the previous snippet, you can use this approach to delay items. +But, the primary use case is about completing asynchronous actions such as calling an asynchronous `close` method on a resource: + +```java linenums="1" +{=snippet:insert("java/tutorials/ObserveTest.java", "close")} +``` + +Under the hood, Mutiny gets the `Uni` (by invoking the callback) and subscribes to it. +It observes the item or failure event from that Uni. +It discards the item value as only the emission matters in this case. + +If the callback throws an exception or the produced `Uni` produces a failure, Mutiny propagates that failure (or a `CompositeException`) downstream, replacing the original event. + +## Summary + +- The `invoke` and `call` methods are handy when you need to observe a `Uni` or a `Multi` without changing the transiting events. +- Use `invoke` for implementing synchronous side-effects or logging events. +- The asynchronous nature of `call` makes it perfect for implementing asynchronous side-effects, such as closing resources, flushing data, delay items, and so on. + +The following table highlights the key differences: + +| | `invoke` | `call` | +|--------------------:|:----------------------------------|:-------------------------------------------------| +| **Nature** | synchronous | asynchronous | +| **Return type** | `void` | `Uni` | +| **Main use cases** | logging, synchronous side-effects | I/O operations, closing resources, flushing data | + diff --git a/documentation/docs-roq/content/3.3.0/tutorials/retrying.md b/documentation/docs-roq/content/3.3.0/tutorials/retrying.md new file mode 100644 index 000000000..a40970aee --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/tutorials/retrying.md @@ -0,0 +1,62 @@ +--- +title: "Retrying on failures" +layout: page +tags: +- tutorial +- beginner +--- + +# Retrying on failures + +It is common to want to retry if something terrible happened. + +You can retry upon failure. +The [How does retry... retries](https://quarkus.io/blog/uni-retry/) blog post provides a more detailed overview of the retry mechanism. + +> [!NOTE] +> If despite multiple attempts, it still fails, the failure is propagated downstream. + +## Retry multiple times + +To retry on failure, use `onFailure().retry()`: + + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-at-most")} +``` + +You pass the number of retries as a parameter. + +> [!IMPORTANT] +> While `.onFailure().retry().indefinitely()` is available, it may never terminate, so use it with caution. + +## Introducing delays + +By default, `retry` retries immediately. +When using remote services, it is often better to delay a bit the attempts. + +Mutiny provides a method to configure an exponential backoff: a growing delay between retries. +Configure the exponential backoff as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-backoff")} +``` + +The backoff is configured with the initial and max delay. +Optionally, you can also configure a jitter to add a pinch of randomness to the delay. + +When using exponential backoff, you may not want to configure the max number of attempts (`atMost`), but a deadline. +To do so, use either `expireIn` or `expireAt`. + +## Deciding to retry + +As an alternative to `atMost`, you can also use `until`. +This method accepts a predicate called after every failure. +When used, a backoff should not be used. + +If the predicate returned `true,` it retries. +Otherwise, it stops retrying and propagates the last failure downstream: + +```java linenums="1" +{=snippet:insert("java/tutorials/RetryTest.java", "retry-until")} +``` diff --git a/documentation/docs-roq/content/3.3.0/tutorials/transforming-items-asynchronously.md b/documentation/docs-roq/content/3.3.0/tutorials/transforming-items-asynchronously.md new file mode 100644 index 000000000..667241d64 --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/tutorials/transforming-items-asynchronously.md @@ -0,0 +1,150 @@ +--- +title: "Transforming items asynchronously" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items asynchronously + +The previous examples illustrated how to transform each item from a stream into another item. +Yet, there are cases where we need to go beyond this, for example: + +- Transforming an item into a `Uni` -- any asynchronous processing such as calling a remote service, interacting with a database, etc +- Transforming an item into a `Multi` -- producing a multi-items stream based on the incoming item, filtering out items, etc + +Having the possibility to transform an item into a stream gives us many opportunities. +To implement such transformations, we use `onItem().transformToUni(Function>)` and `onItem().transformToMulti(Function>)` + +## Uni - Transforming an item into a Uni + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Uni(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(result) + O->>D: onItem(result) +``` + +Imagine that you have a `Uni`, and you want to call a remote service. + +Calling a remote service is an asynchronous action represented by a `Uni`, as in: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "service")} +``` + +To call this service, you need to transform the item received from the first `Uni` into the `Uni` returned by the service: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "call")} +``` + +This snippet _chains_ the first `Uni` with another one. +The returned `Uni` (`result`) emits the result from the remote service or a failure if anything wrong happened: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "chain")} +``` + +## Uni - Transforming an item into a Multi + +The previous example produced a single item. +You may want to transform the received item into a stream which is... a `Multi`! + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi")} +``` + +This code creates a stream of two elements, duplicating the received item. + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "uni-to-multi-2")} +``` + +```mermaid +sequenceDiagram + autonumber + participant M as Uni + participant O as Transformer + participant U as Multi(item) + participant D as Subscriber + + M->>O: onItem(item) + O--)U: subscribe(...) + U--)O: onSubscribe(sub) + U--)O: onItem(item) + O->>D: onItem(item) + U--)O: onItem(item) + O->>D: onItem(item) +``` + +The produced `Multi` objects can of course be more complicated than that and emit items in an asynchronous fashion. + +## Transforming items from Multi - the _merge_ vs _concatenate_ dilemma + +When transforming items emitted by an upstream `Multi,` we need to answer the following question: how are we going to _merge_ the produced items back? + +Let's take an example. +Imagine a `Multi` emitting the `Cameron` and `Donna` items (in order), and you want to call the `invokeRemoteGreetingService` from above. +It thus calls `invokeRemoteGreetingService("Cameron")` then `invokeRemoteGreetingService("Donna")`. + +The service does not have a constant response time (because of network latency or the load), which means that responses can be interleaved. +Indeed, you may receive `"Hello Donna"` before `"Hello Cameron"`. + +Now, how do you want to handle this case? +Do you need to preserve the order and ensure that the downstream subscriber will always get `"Hello Cameron"` first, or do you accept interleaved responses? + +When transforming items from `Multi` into streams, you need to decide in which order the items emitted by the produced stream are going to be received by the downstream subscriber. +Mutiny offers two possibilities: + +1. **Merging** -- it does not preserve the order and emits the items from the produced streams as they come, or +2. **Concatenating** -- it maintains and concatenates the streams produced for each item. + +## Multi - Transforming an item into a Uni + +To implement the scenario from the last section, you will use `onItem().transformToUniAndMerge` or `onItem().transformToUniAndConcatenate()` depending on your ordering choice: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat")} +``` + +> [!IMPORTANT] +> - When merging: items from the source `Multi` _may_ be processed **concurrently** depending on the concurrency level that has been set, if any. +> - When concatenating: items from the source `Multi` are processed **in order**, waiting for each `Uni` to complete before moving on to the next item. + +### Controlling concurrency with merge + +The `merge` method accepts an optional `concurrency` parameter that limits how many inner streams can be subscribed to concurrently: + +```java +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concurrency")} +``` + +When not provided, the default concurrency is configured using `Infrastructure.getBufferSizeS()`. + +> [!WARNING] +> **Merge concurrency** +> When using merge with limited concurrency, be aware of potential backpressure issues. +> **Setting concurrency too low** can cause upstream request starvation if the number of subscribed but not emitting inner streams surpasses the level of concurrency. +> **Unbounded concurrency** eliminates the request starvation issue by removing the limit on the number of subscribed inner streams to merge. + + +## Multi - Transforming an item into a Multi + +`onItem().transformToMultiAndMerge` and `onItem().transformToMultiAndConcatenate` transform incoming items into `Multi` streams. +The produced `Multi` objects are either _merged_ or _concatenated_: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsAsyncTest.java", "merge-concat-multi")} +``` + +Like after `transformToUni`, the `merge` method after `transformToMulti` also accepts an optional concurrency parameter with the same considerations regarding backpressure and request starvation when used with infinite streams. diff --git a/documentation/docs-roq/content/3.3.0/tutorials/transforming-items.md b/documentation/docs-roq/content/3.3.0/tutorials/transforming-items.md new file mode 100644 index 000000000..8905b9f9c --- /dev/null +++ b/documentation/docs-roq/content/3.3.0/tutorials/transforming-items.md @@ -0,0 +1,69 @@ +--- +title: "Transforming items" +layout: page +tags: +- tutorial +- beginner +--- + +# Transforming items + +Both `Unis` and `Multis` emit _items_. + +One of the most common operations you will do is transforming these items using a _synchronous_ 1-to-1 function. + +To achieve this, you use `onItem().transform(Function)`. +It calls the passed function for each item and produces the result as an item which is propagated downstream. + +```mermaid +sequenceDiagram + autonumber + participant M as Multi + participant O as Transformer + participant D as Subscriber + + M->>O: onItem(a1) + O->>D: onItem(a2) + + M->>O: onItem(b1) + O->>D: onItem(b2) + + M->>O: onItem(c1) + O->>D: onItem(c2) +``` + +## Transforming items produced by a Uni + +Let's imagine you have a `Uni,` and you want to capitalize the received `String`. +Implementing this transformation is done as follows: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "uni-transform")} +``` + +## Transforming items produced by a Multi + +The only difference for `Multi` is that the function is called for each item: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform")} +``` + +The produced items are passed to the downstream subscriber: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "multi-transform-2")} +``` + +## What if the transformation failed? + +If the transformation throws an exception, that exception is caught and passed to the downstream subscriber as a _failure_ event. +It also means that the subscriber won't get further item after that failure. + +## Chaining multiple transformations + +You can chain multiple transformations: + +```java linenums="1" +{=snippet:insert("java/tutorials/TransformItemsTest.java", "chain")} +``` diff --git a/documentation/docs-roq/content/dev/tutorials/getting-mutiny.md b/documentation/docs-roq/content/dev/tutorials/getting-mutiny.md new file mode 100644 index 000000000..3f8c3d0bf --- /dev/null +++ b/documentation/docs-roq/content/dev/tutorials/getting-mutiny.md @@ -0,0 +1,10 @@ +--- +title: Getting Mutiny (dev) +layout: page +--- + +# Getting Mutiny (Development Version) + +This is the development version of the documentation. Content here reflects the latest unreleased changes. + +For the stable documentation, please switch to the latest release version using the version selector in the sidebar. diff --git a/documentation/docs-roq/content/index.html b/documentation/docs-roq/content/index.html new file mode 100644 index 000000000..0063d21dc --- /dev/null +++ b/documentation/docs-roq/content/index.html @@ -0,0 +1,79 @@ +--- +title: Mutiny - Intuitive Event-Driven Reactive Programming Library for Java +description: Intuitive Event-Driven Reactive Programming Library for Java +name: SmallRye Mutiny +simple-name: Mutiny +image: logo.png +social-github: smallrye/smallrye-mutiny +layout: home +--- + +
+ +

Mutiny – Intuitive Event-Driven Reactive Programming Library for Java

+ +
Uni<String> request = makeSomeNetworkRequest(params);
+
+request.ifNoItem().after(ofMillis(100))
+    .failWith(() -> new TooSlowException("💥"))
+    .onFailure(IOException.class).recoverWithItem(fail -> "📦")
+    .subscribe().with(
+        item -> log("👍 " + item),
+         err -> log(err.getMessage())
+    );
+ +

+{#for v in cdi:versions.sorted}{#if v.defaultVersion}Get Mutiny {=cdi:attributes.versions.mutiny}{/if}{/for} +

+ +
+ +
+

Event-Driven

+

Mutiny places events at the core of its design.

+

With Mutiny, you observe events, react to them, and create elegant and readable processing pipelines.

+

A PhD in functional programming is not required.

+
+ +
+

Navigable

+

Even with smart code completion, classes with hundreds of methods are confusing.

+

Mutiny provides a navigable and explicit API driving you towards the operator you need.

+
+ +
+

Non-blocking I/O

+

Mutiny is the perfect companion to tame the asynchronous nature of applications with non-blocking I/O.

+

Compose operations in a declarative fashion, transform data, enforce progress, recover from failures and more.

+
+ +
+

Quarkus and Vert.x native

+

Mutiny is integrated in Quarkus where every reactive API uses Mutiny, and Eclipse Vert.x clients are made available using Mutiny bindings.

+

Mutiny is however an independent library that can ultimately be used in any Java application.

+
+ +
+

Made for an asynchronous world

+

Mutiny can be used in any asynchronous application such as event-driven microservices, message-based applications, network utilities, data stream processing, and of course… reactive applications!

+
+ +
+

Reactive Converters Built-In

+

Mutiny is based on the Reactive Streams protocol and Java Flow, and so it can be integrated with any other reactive programming library.

+

In addition, it proposes converters to interact with other popular libraries.

+
+ +
+ + + +
diff --git a/documentation/docs-roq/content/search-index.json b/documentation/docs-roq/content/search-index.json new file mode 100644 index 000000000..cf06489d6 --- /dev/null +++ b/documentation/docs-roq/content/search-index.json @@ -0,0 +1 @@ +{#include fm/search-index.json} \ No newline at end of file diff --git a/documentation/docs-roq/convert-mkdocs.py b/documentation/docs-roq/convert-mkdocs.py new file mode 100644 index 000000000..c7c6950c3 --- /dev/null +++ b/documentation/docs-roq/convert-mkdocs.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +"""Convert a MkDocs markdown file to Roq format.""" + +import re +import sys +import os + + +def strip_frontmatter(lines): + """Remove YAML frontmatter and return (tags_list, body_lines).""" + tags = [] + if not lines or lines[0].strip() != '---': + return tags, lines + + end = -1 + for i in range(1, len(lines)): + if lines[i].strip() == '---': + end = i + break + if end == -1: + return tags, lines + + fm_lines = lines[1:end] + in_tags = False + for line in fm_lines: + if line.strip().startswith('tags:'): + in_tags = True + continue + if in_tags: + m = re.match(r'^\s*-\s+(.+)', line) + if m: + tags.append(m.group(1).strip()) + else: + in_tags = False + return tags, lines[end + 1:] + + +def extract_title(lines): + """Extract title from first H1 heading.""" + for line in lines: + if line.startswith('# '): + return line[2:].strip() + return None + + +def convert_macros(text): + """Convert MkDocs macros to Qute template expressions.""" + # {{ insert('file', 'tag') }} or {{ insert("file", "tag") }} + text = re.sub( + r"\{\{\s*insert\(['\"]([^'\"]+)['\"],\s*['\"]([^'\"]+)['\"]\)\s*\}\}", + r'{=snippet:insert("\1", "\2")}', + text + ) + # {{ insert('file') }} or {{ insert("file") }} + text = re.sub( + r"\{\{\s*insert\(['\"]([^'\"]+)['\"]\)\s*\}\}", + r'{=snippet:insert("\1")}', + text + ) + # {{ attributes.xxx }} + text = re.sub( + r"\{\{\s*attributes\.([a-zA-Z0-9_.]+)\s*\}\}", + r'{=cdi:attributes.\1}', + text + ) + # Fix snake_case attribute names to match Java record accessors + text = text.replace('cdi:attributes.versions.vertx_bindings', + 'cdi:attributes.versions.vertxBindings') + return text + + +def convert_tabs(lines): + """Convert MkDocs tabbed content === 'Tab' to #### Tab with dedented body.""" + result = [] + in_tab = False + for line in lines: + m = re.match(r'^=== "(.+)"', line) + if m: + in_tab = True + result.append(f'#### {m.group(1)}\n') + continue + if in_tab: + if line.startswith(' '): + result.append(line[4:]) + continue + elif line.strip() == '': + result.append(line) + continue + else: + in_tab = False + result.append(line) + return result + + +def convert_admonitions(lines): + """Convert MkDocs admonitions to GFM alerts or
.""" + type_map = { + 'INFO': 'NOTE', 'SUCCESS': 'TIP', 'EXAMPLE': 'NOTE', 'QUOTE': 'NOTE', + 'DANGER': 'CAUTION', 'BUG': 'WARNING', 'ABSTRACT': 'NOTE', + 'QUESTION': 'NOTE', 'FAILURE': 'WARNING' + } + valid_types = {'NOTE', 'TIP', 'IMPORTANT', 'WARNING', 'CAUTION'} + + result = [] + i = 0 + while i < len(lines): + line = lines[i] + m = re.match(r'^(!!!|\?\?\?) +(\w+)( +"([^"]+)")?\s*$', line) + if m: + marker = m.group(1) + adm_type = m.group(2).upper() + title = m.group(4) or '' + + mapped = type_map.get(adm_type, adm_type) + if mapped not in valid_types: + mapped = 'NOTE' + + i += 1 + # Collect indented content + content_lines = [] + while i < len(lines): + if lines[i].startswith(' '): + content_lines.append(lines[i][4:]) + elif lines[i].strip() == '': + # Blank line: include only if next line is still indented + if i + 1 < len(lines) and lines[i + 1].startswith(' '): + content_lines.append('\n') + else: + break + else: + break + i += 1 + + if marker == '???': + result.append('
\n') + summary = title if title else adm_type.title() + result.append(f'{summary}\n') + result.append('\n') + result.extend(content_lines) + result.append('\n') + result.append('
\n') + else: + result.append(f'> [!{mapped}]\n') + if title and title.lower() != adm_type.lower(): + result.append(f'> **{title}**\n') + for cl in content_lines: + if cl.strip() == '': + result.append('>\n') + else: + result.append(f'> {cl}') + continue + + result.append(line) + i += 1 + return result + + +def convert_file(src_path, dst_path): + with open(src_path, 'r', encoding='utf-8') as f: + lines = f.readlines() + + tags, body_lines = strip_frontmatter(lines) + title = extract_title(body_lines) + if not title: + title = os.path.splitext(os.path.basename(dst_path))[0] + + # Escape double quotes in title for YAML + title = title.replace('"', '\\"') + + # Build body text + body = ''.join(body_lines) + + # Convert macros + body = convert_macros(body) + + # Convert tabs + body_lines = convert_tabs(body.splitlines(keepends=True)) + + # Convert admonitions + body_lines = convert_admonitions(body_lines) + + # Write output + with open(dst_path, 'w', encoding='utf-8') as f: + f.write('---\n') + f.write(f'title: "{title}"\n') + f.write('layout: page\n') + if tags: + f.write('tags:\n') + for t in tags: + f.write(f'- {t}\n') + f.write('---\n') + f.writelines(body_lines) + + +if __name__ == '__main__': + convert_file(sys.argv[1], sys.argv[2]) diff --git a/documentation/docs-roq/data/attributes.yml b/documentation/docs-roq/data/attributes.yml new file mode 100644 index 000000000..d03eb419c --- /dev/null +++ b/documentation/docs-roq/data/attributes.yml @@ -0,0 +1,4 @@ +project-version: 3.3.0 +versions: + mutiny: 3.3.0 + vertx_bindings: 4.0.0-beta2 diff --git a/documentation/docs-roq/data/authors.yml b/documentation/docs-roq/data/authors.yml new file mode 100644 index 000000000..7227b8378 --- /dev/null +++ b/documentation/docs-roq/data/authors.yml @@ -0,0 +1,3 @@ +smallrye: + name: SmallRye + url: https://smallrye.io/ diff --git a/documentation/docs-roq/data/menu.yml b/documentation/docs-roq/data/menu.yml new file mode 100644 index 000000000..703771fc4 --- /dev/null +++ b/documentation/docs-roq/data/menu.yml @@ -0,0 +1,40 @@ +items: + - title: "Home" + path: "/" + icon: "fa-solid fa-house" + position: "top-nav" + - title: "Tutorials" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-graduation-cap" + position: "top-nav" + section: "Tutorials" + - title: "Guides" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + position: "top-nav" + section: "Guides" + - title: "Reference" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file-lines" + position: "top-nav" + section: "Reference" + - title: "API" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + position: "top-nav" + target: "_blank" + - title: "Discussions/Questions" + path: "https://github.com/smallrye/smallrye-mutiny/discussions" + icon: "fa-solid fa-comments" + position: "top-nav" + target: "_blank" + - title: "GitHub" + path: "https://github.com/smallrye/smallrye-mutiny" + icon: "fa-brands fa-github" + target: "_blank" + position: "bottom" + - title: "SmallRye" + path: "https://smallrye.io/" + icon: "fa-solid fa-arrow-up-right-from-square" + target: "_blank" + position: "bottom" diff --git a/documentation/docs-roq/data/versions/2.0.0.yml b/documentation/docs-roq/data/versions/2.0.0.yml new file mode 100644 index 000000000..501324cc8 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.0.0.yml @@ -0,0 +1,160 @@ +label: "2.0.0" +path: "2.0.0" +sortOrder: 33 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - name: "Guides" + items: + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.1.0.yml b/documentation/docs-roq/data/versions/2.1.0.yml new file mode 100644 index 000000000..0ee083795 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.1.0.yml @@ -0,0 +1,160 @@ +label: "2.1.0" +path: "2.1.0" +sortOrder: 32 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - name: "Guides" + items: + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.2.0.yml b/documentation/docs-roq/data/versions/2.2.0.yml new file mode 100644 index 000000000..81bd98025 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.2.0.yml @@ -0,0 +1,160 @@ +label: "2.2.0" +path: "2.2.0" +sortOrder: 31 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - name: "Guides" + items: + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.3.0.yml b/documentation/docs-roq/data/versions/2.3.0.yml new file mode 100644 index 000000000..49ec7d32e --- /dev/null +++ b/documentation/docs-roq/data/versions/2.3.0.yml @@ -0,0 +1,160 @@ +label: "2.3.0" +path: "2.3.0" +sortOrder: 30 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - name: "Guides" + items: + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.3.1.yml b/documentation/docs-roq/data/versions/2.3.1.yml new file mode 100644 index 000000000..750125414 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.3.1.yml @@ -0,0 +1,160 @@ +label: "2.3.1" +path: "2.3.1" +sortOrder: 29 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - name: "Guides" + items: + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.4.0.yml b/documentation/docs-roq/data/versions/2.4.0.yml new file mode 100644 index 000000000..6eebdca23 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.4.0.yml @@ -0,0 +1,163 @@ +label: "2.4.0" +path: "2.4.0" +sortOrder: 28 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - name: "Guides" + items: + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.5.0.yml b/documentation/docs-roq/data/versions/2.5.0.yml new file mode 100644 index 000000000..3da4fcb84 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.5.0.yml @@ -0,0 +1,169 @@ +label: "2.5.0" +path: "2.5.0" +sortOrder: 27 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.5.1.yml b/documentation/docs-roq/data/versions/2.5.1.yml new file mode 100644 index 000000000..78b6fe702 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.5.1.yml @@ -0,0 +1,169 @@ +label: "2.5.1" +path: "2.5.1" +sortOrder: 26 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.5.2.yml b/documentation/docs-roq/data/versions/2.5.2.yml new file mode 100644 index 000000000..d9be13860 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.5.2.yml @@ -0,0 +1,172 @@ +label: "2.5.2" +path: "2.5.2" +sortOrder: 25 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.5.3.yml b/documentation/docs-roq/data/versions/2.5.3.yml new file mode 100644 index 000000000..0318f45b0 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.5.3.yml @@ -0,0 +1,172 @@ +label: "2.5.3" +path: "2.5.3" +sortOrder: 24 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.5.4.yml b/documentation/docs-roq/data/versions/2.5.4.yml new file mode 100644 index 000000000..13b1205dc --- /dev/null +++ b/documentation/docs-roq/data/versions/2.5.4.yml @@ -0,0 +1,172 @@ +label: "2.5.4" +path: "2.5.4" +sortOrder: 23 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.5.5.yml b/documentation/docs-roq/data/versions/2.5.5.yml new file mode 100644 index 000000000..8222174be --- /dev/null +++ b/documentation/docs-roq/data/versions/2.5.5.yml @@ -0,0 +1,172 @@ +label: "2.5.5" +path: "2.5.5" +sortOrder: 22 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.5.6.yml b/documentation/docs-roq/data/versions/2.5.6.yml new file mode 100644 index 000000000..d819b21a7 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.5.6.yml @@ -0,0 +1,172 @@ +label: "2.5.6" +path: "2.5.6" +sortOrder: 21 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.6.0.yml b/documentation/docs-roq/data/versions/2.6.0.yml new file mode 100644 index 000000000..520cfd626 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.6.0.yml @@ -0,0 +1,175 @@ +label: "2.6.0" +path: "2.6.0" +sortOrder: 20 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.6.1.yml b/documentation/docs-roq/data/versions/2.6.1.yml new file mode 100644 index 000000000..c7b74b1a0 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.6.1.yml @@ -0,0 +1,175 @@ +label: "2.6.1" +path: "2.6.1" +sortOrder: 19 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.6.2.yml b/documentation/docs-roq/data/versions/2.6.2.yml new file mode 100644 index 000000000..8d2df8979 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.6.2.yml @@ -0,0 +1,175 @@ +label: "2.6.2" +path: "2.6.2" +sortOrder: 18 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.7.0.yml b/documentation/docs-roq/data/versions/2.7.0.yml new file mode 100644 index 000000000..bfd6abb29 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.7.0.yml @@ -0,0 +1,175 @@ +label: "2.7.0" +path: "2.7.0" +sortOrder: 17 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.8.0.yml b/documentation/docs-roq/data/versions/2.8.0.yml new file mode 100644 index 000000000..b653a2d1f --- /dev/null +++ b/documentation/docs-roq/data/versions/2.8.0.yml @@ -0,0 +1,175 @@ +label: "2.8.0" +path: "2.8.0" +sortOrder: 16 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.9.0.yml b/documentation/docs-roq/data/versions/2.9.0.yml new file mode 100644 index 000000000..c0ecd5b45 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.9.0.yml @@ -0,0 +1,175 @@ +label: "2.9.0" +path: "2.9.0" +sortOrder: 15 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.9.1.yml b/documentation/docs-roq/data/versions/2.9.1.yml new file mode 100644 index 000000000..05c0da233 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.9.1.yml @@ -0,0 +1,175 @@ +label: "2.9.1" +path: "2.9.1" +sortOrder: 14 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.9.2.yml b/documentation/docs-roq/data/versions/2.9.2.yml new file mode 100644 index 000000000..8184ada29 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.9.2.yml @@ -0,0 +1,175 @@ +label: "2.9.2" +path: "2.9.2" +sortOrder: 13 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.9.3.yml b/documentation/docs-roq/data/versions/2.9.3.yml new file mode 100644 index 000000000..7a0907c15 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.9.3.yml @@ -0,0 +1,175 @@ +label: "2.9.3" +path: "2.9.3" +sortOrder: 12 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.9.4.yml b/documentation/docs-roq/data/versions/2.9.4.yml new file mode 100644 index 000000000..f0fd03558 --- /dev/null +++ b/documentation/docs-roq/data/versions/2.9.4.yml @@ -0,0 +1,175 @@ +label: "2.9.4" +path: "2.9.4" +sortOrder: 11 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/2.9.5.yml b/documentation/docs-roq/data/versions/2.9.5.yml new file mode 100644 index 000000000..32cebb60c --- /dev/null +++ b/documentation/docs-roq/data/versions/2.9.5.yml @@ -0,0 +1,175 @@ +label: "2.9.5" +path: "2.9.5" +sortOrder: 10 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/3.0.0.yml b/documentation/docs-roq/data/versions/3.0.0.yml new file mode 100644 index 000000000..1fbb6d193 --- /dev/null +++ b/documentation/docs-roq/data/versions/3.0.0.yml @@ -0,0 +1,175 @@ +label: "3.0.0" +path: "3.0.0" +sortOrder: 9 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/3.0.1.yml b/documentation/docs-roq/data/versions/3.0.1.yml new file mode 100644 index 000000000..98b30ccef --- /dev/null +++ b/documentation/docs-roq/data/versions/3.0.1.yml @@ -0,0 +1,175 @@ +label: "3.0.1" +path: "3.0.1" +sortOrder: 8 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/3.0.2.yml b/documentation/docs-roq/data/versions/3.0.2.yml new file mode 100644 index 000000000..02f6b193f --- /dev/null +++ b/documentation/docs-roq/data/versions/3.0.2.yml @@ -0,0 +1,175 @@ +label: "3.0.2" +path: "3.0.2" +sortOrder: 7 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/3.0.3.yml b/documentation/docs-roq/data/versions/3.0.3.yml new file mode 100644 index 000000000..476eec25e --- /dev/null +++ b/documentation/docs-roq/data/versions/3.0.3.yml @@ -0,0 +1,175 @@ +label: "3.0.3" +path: "3.0.3" +sortOrder: 6 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/3.1.0.yml b/documentation/docs-roq/data/versions/3.1.0.yml new file mode 100644 index 000000000..27648c8d5 --- /dev/null +++ b/documentation/docs-roq/data/versions/3.1.0.yml @@ -0,0 +1,175 @@ +label: "3.1.0" +path: "3.1.0" +sortOrder: 5 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/3.1.1.yml b/documentation/docs-roq/data/versions/3.1.1.yml new file mode 100644 index 000000000..653f9b376 --- /dev/null +++ b/documentation/docs-roq/data/versions/3.1.1.yml @@ -0,0 +1,178 @@ +label: "3.1.1" +path: "3.1.1" +sortOrder: 4 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "Grouping items from Multi" + path: "/guides/grouping-items" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/3.2.0.yml b/documentation/docs-roq/data/versions/3.2.0.yml new file mode 100644 index 000000000..22bc59c72 --- /dev/null +++ b/documentation/docs-roq/data/versions/3.2.0.yml @@ -0,0 +1,178 @@ +label: "3.2.0" +path: "3.2.0" +sortOrder: 3 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "Grouping items from Multi" + path: "/guides/grouping-items" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/3.2.1.yml b/documentation/docs-roq/data/versions/3.2.1.yml new file mode 100644 index 000000000..501c0deae --- /dev/null +++ b/documentation/docs-roq/data/versions/3.2.1.yml @@ -0,0 +1,178 @@ +label: "3.2.1" +path: "3.2.1" +sortOrder: 2 +defaultVersion: false +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting started with Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny!" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating `Uni` pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating `Multi` pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming items asynchronously" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying on failures" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Go further with the Mutiny workshop!" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "How to do branching in a reactive pipeline?" + path: "/guides/branching" + icon: "fa-solid fa-book" + - title: "Broadcasting to multiple subscribers (like server-sent events, websockets, etc)" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-book" + - title: "Collecting items from Multi" + path: "/guides/collecting-items" + icon: "fa-solid fa-book" + - title: "Combining items from streams" + path: "/guides/combining-items" + icon: "fa-solid fa-book" + - title: "How to deal with CompletionStage?" + path: "/guides/completion-stage" + icon: "fa-solid fa-book" + - title: "Context passing" + path: "/guides/context-passing" + icon: "fa-solid fa-book" + - title: "Controlling the demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-book" + - title: "Using other reactive programming libraries" + path: "/guides/converters" + icon: "fa-solid fa-book" + - title: "Can I have custom operators?" + path: "/guides/custom-operators" + icon: "fa-solid fa-book" + - title: "How to delay events?" + path: "/guides/delaying-events" + icon: "fa-solid fa-book" + - title: "How to deal with dropped exceptions?" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-book" + - title: "Eliminate duplicates and repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-book" + - title: "How to change the emission thread?" + path: "/guides/emission-threads" + icon: "fa-solid fa-book" + - title: "What is the difference between emitOn and runSubscriptionOn?" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-book" + - title: "Filtering items from Multi" + path: "/guides/filtering-items" + icon: "fa-solid fa-book" + - title: "How can I integrate Mutiny with my framework?" + path: "/guides/framework-integration" + icon: "fa-solid fa-book" + - title: "Grouping items from Multi" + path: "/guides/grouping-items" + icon: "fa-solid fa-book" + - title: "How to handle null?" + path: "/guides/handling-null" + icon: "fa-solid fa-book" + - title: "How to handle timeouts?" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-book" + - title: "Hot streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-book" + - title: "From imperative to reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "How can I create a Multi from a non-reactive source?" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-book" + - title: "Joining several unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-book" + - title: "Kotlin integration" + path: "/guides/kotlin" + icon: "fa-solid fa-book" + - title: "Logging events" + path: "/guides/logging" + icon: "fa-solid fa-book" + - title: "Merging and Concatenating Streams" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-book" + - title: "Splitting a Multi into several Multi" + path: "/guides/multi-split" + icon: "fa-solid fa-book" + - title: "How to use paginated APIs?" + path: "/guides/pagination" + icon: "fa-solid fa-book" + - title: "How to use polling?" + path: "/guides/polling" + icon: "fa-solid fa-book" + - title: "From reactive to imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-book" + - title: "Using map, flatMap and concatMap" + path: "/guides/rx" + icon: "fa-solid fa-book" + - title: "Shortcut methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-book" + - title: "Spying on events" + path: "/guides/spies" + icon: "fa-solid fa-book" + - title: "Take/Skip the first or last items" + path: "/guides/take-skip-items" + icon: "fa-solid fa-book" + - title: "How can I write unit / integration tests?" + path: "/guides/testing" + icon: "fa-solid fa-book" + - title: "Dealing with checked exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - name: "Reference" + items: + - title: "Going reactive: a few pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "What is Reactive Programming?" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What makes Mutiny different?" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Why is asynchronous important?" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/3.3.0.yml b/documentation/docs-roq/data/versions/3.3.0.yml new file mode 100644 index 000000000..c6c59b024 --- /dev/null +++ b/documentation/docs-roq/data/versions/3.3.0.yml @@ -0,0 +1,178 @@ +label: "3.3.0" +path: "3.3.0" +sortOrder: 1 +defaultVersion: true +devVersion: false +sections: + - name: "Tutorials" + items: + - title: "Getting Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" + - title: "Hello Mutiny" + path: "/tutorials/hello-mutiny" + icon: "fa-solid fa-hand-wave" + - title: "Creating Uni Pipelines" + path: "/tutorials/creating-uni-pipelines" + icon: "fa-solid fa-code" + - title: "Creating Multi Pipelines" + path: "/tutorials/creating-multi-pipelines" + icon: "fa-solid fa-code" + - title: "Observing Events" + path: "/tutorials/observing-events" + icon: "fa-solid fa-eye" + - title: "Transforming Items" + path: "/tutorials/transforming-items" + icon: "fa-solid fa-shuffle" + - title: "Transforming Items Async" + path: "/tutorials/transforming-items-asynchronously" + icon: "fa-solid fa-shuffle" + - title: "Handling Failures" + path: "/tutorials/handling-failures" + icon: "fa-solid fa-triangle-exclamation" + - title: "Retrying" + path: "/tutorials/retrying" + icon: "fa-solid fa-rotate" + - title: "Mutiny Workshop" + path: "/tutorials/mutiny-workshop" + icon: "fa-solid fa-flask" + - name: "Guides" + items: + - title: "Imperative to Reactive" + path: "/guides/imperative-to-reactive" + icon: "fa-solid fa-book" + - title: "Reactive to Imperative" + path: "/guides/reactive-to-imperative" + icon: "fa-solid fa-book" + - title: "Unchecked Exceptions" + path: "/guides/unchecked-exceptions" + icon: "fa-solid fa-book" + - title: "Filtering Items" + path: "/guides/filtering-items" + icon: "fa-solid fa-filter" + - title: "Collecting Items" + path: "/guides/collecting-items" + icon: "fa-solid fa-box" + - title: "Grouping Items" + path: "/guides/grouping-items" + icon: "fa-solid fa-layer-group" + - title: "Take and Skip" + path: "/guides/take-skip-items" + icon: "fa-solid fa-forward" + - title: "Duplicates & Repetitions" + path: "/guides/eliminate-duplicates-and-repetitions" + icon: "fa-solid fa-clone" + - title: "Handling Null" + path: "/guides/handling-null" + icon: "fa-solid fa-ban" + - title: "Handling Timeouts" + path: "/guides/handling-timeouts" + icon: "fa-solid fa-clock" + - title: "Delaying Events" + path: "/guides/delaying-events" + icon: "fa-solid fa-hourglass" + - title: "Pagination" + path: "/guides/pagination" + icon: "fa-solid fa-book-open" + - title: "Polling" + path: "/guides/polling" + icon: "fa-solid fa-arrows-rotate" + - title: "Emission Threads" + path: "/guides/emission-threads" + icon: "fa-solid fa-microchip" + - title: "EmitOn vs RunSubscriptionOn" + path: "/guides/emit-on-vs-run-subscription-on" + icon: "fa-solid fa-microchip" + - title: "Completion Stage" + path: "/guides/completion-stage" + icon: "fa-solid fa-check-double" + - title: "RxJava" + path: "/guides/rx" + icon: "fa-solid fa-arrows-spin" + - title: "Shortcut Methods" + path: "/guides/shortcut-methods" + icon: "fa-solid fa-bolt" + - title: "Merging & Concatenating" + path: "/guides/merging-and-concatenating-streams" + icon: "fa-solid fa-code-merge" + - title: "Combining Items" + path: "/guides/combining-items" + icon: "fa-solid fa-object-group" + - title: "Joining Unis" + path: "/guides/joining-unis" + icon: "fa-solid fa-link" + - title: "Converters" + path: "/guides/converters" + icon: "fa-solid fa-right-left" + - title: "Testing" + path: "/guides/testing" + icon: "fa-solid fa-vial" + - title: "Spies" + path: "/guides/spies" + icon: "fa-solid fa-magnifying-glass" + - title: "Custom Operators" + path: "/guides/custom-operators" + icon: "fa-solid fa-puzzle-piece" + - title: "Dropped Exceptions" + path: "/guides/dropped-exceptions" + icon: "fa-solid fa-trash" + - title: "Framework Integration" + path: "/guides/framework-integration" + icon: "fa-solid fa-plug" + - title: "Hot Streams" + path: "/guides/hot-streams" + icon: "fa-solid fa-fire" + - title: "Non-Reactive Sources" + path: "/guides/integrate-a-non-reactive-source" + icon: "fa-solid fa-plug" + - title: "Kotlin" + path: "/guides/kotlin" + icon: "fa-solid fa-k" + - title: "Logging" + path: "/guides/logging" + icon: "fa-solid fa-list" + - title: "Context Passing" + path: "/guides/context-passing" + icon: "fa-solid fa-share" + - title: "Replaying Multis" + path: "/guides/replaying-multis" + icon: "fa-solid fa-play" + - title: "Controlling Demand" + path: "/guides/controlling-demand" + icon: "fa-solid fa-gauge" + - title: "Multi Split" + path: "/guides/multi-split" + icon: "fa-solid fa-code-branch" + - title: "Branching" + path: "/guides/branching" + icon: "fa-solid fa-code-branch" + - title: "Broadcasting" + path: "/guides/broadcasting-to-multiple-subscribers" + icon: "fa-solid fa-tower-broadcast" + - name: "Reference" + items: + - title: "Migrating to Mutiny 2" + path: "/reference/migrating-to-mutiny-2" + icon: "fa-solid fa-file" + - title: "Why Async Matters" + path: "/reference/why-is-asynchronous-important" + icon: "fa-solid fa-file" + - title: "What Is Reactive Programming" + path: "/reference/what-is-reactive-programming" + icon: "fa-solid fa-file" + - title: "What Makes Mutiny Different" + path: "/reference/what-makes-mutiny-different" + icon: "fa-solid fa-file" + - title: "Uni and Multi" + path: "/reference/uni-and-multi" + icon: "fa-solid fa-file" + - title: "Going Reactive Pitfalls" + path: "/reference/going-reactive-a-few-pitfalls" + icon: "fa-solid fa-file" + - title: "Publications" + path: "/reference/publications" + icon: "fa-solid fa-file" + - title: "API (Javadoc)" + path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html" + icon: "fa-solid fa-file-code" + target: "_blank" diff --git a/documentation/docs-roq/data/versions/dev.yml b/documentation/docs-roq/data/versions/dev.yml new file mode 100644 index 000000000..51254bffb --- /dev/null +++ b/documentation/docs-roq/data/versions/dev.yml @@ -0,0 +1,11 @@ +label: "Development (unreleased)" +path: "dev" +sortOrder: 999 +defaultVersion: false +devVersion: true +sections: + - name: "Tutorials" + items: + - title: "Getting Mutiny" + path: "/tutorials/getting-mutiny" + icon: "fa-solid fa-download" diff --git a/documentation/docs-roq/extract-versions.sh b/documentation/docs-roq/extract-versions.sh new file mode 100644 index 000000000..be6c0e9ae --- /dev/null +++ b/documentation/docs-roq/extract-versions.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# Extract real documentation from git for each version and convert to Roq format +set -e + +REPO_ROOT="/home/ehugonne/tmp/smallrye-mutiny" +ROQ_DIR="$REPO_ROOT/documentation/docs-roq" +CONTENT_DIR="$ROQ_DIR/content" +DATA_DIR="$ROQ_DIR/data/versions" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +MKDOCS_VERSIONS=( + 2.0.0 2.1.0 2.2.0 2.3.0 2.3.1 2.4.0 + 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 + 2.6.0 2.6.1 2.6.2 2.7.0 2.8.0 + 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 + 3.0.0 3.0.1 3.0.2 3.0.3 + 3.1.0 3.1.1 + 3.2.0 3.2.1 +) + +cd "$REPO_ROOT" + +echo "=== Extracting versioned docs ===" + +declare -A SORT_ORDERS +order=2 +for v in 3.2.1 3.2.0 3.1.1 3.1.0 3.0.3 3.0.2 3.0.1 3.0.0 2.9.5 2.9.4 2.9.3 2.9.2 2.9.1 2.9.0 2.8.0 2.7.0 2.6.2 2.6.1 2.6.0 2.5.6 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5.0 2.4.0 2.3.1 2.3.0 2.2.0 2.1.0 2.0.0; do + SORT_ORDERS[$v]=$order + ((order++)) +done + +for VERSION in "${MKDOCS_VERSIONS[@]}"; do + echo "--- Processing $VERSION ---" + + VERSION_DIR="$CONTENT_DIR/$VERSION" + + if ! git show "$VERSION:documentation/docs/tutorials/" >/dev/null 2>&1; then + echo " SKIP: no MkDocs docs at tag $VERSION" + continue + fi + + rm -rf "$VERSION_DIR" + mkdir -p "$VERSION_DIR/tutorials" "$VERSION_DIR/guides" "$VERSION_DIR/reference" + + TMPDIR=$(mktemp -d) + + for subdir in tutorials guides reference; do + git ls-tree --name-only "$VERSION" "documentation/docs/$subdir/" 2>/dev/null | while read filepath; do + filename=$(basename "$filepath") + if [[ "$filename" == *.md ]]; then + git show "$VERSION:$filepath" > "$TMPDIR/$filename" + python3 "$SCRIPT_DIR/convert-mkdocs.py" "$TMPDIR/$filename" "$VERSION_DIR/$subdir/$filename" + rm "$TMPDIR/$filename" + fi + done + done + + if git show "$VERSION:documentation/docs/tags-index.md" >/dev/null 2>&1; then + git show "$VERSION:documentation/docs/tags-index.md" > "$TMPDIR/tags-index.md" + python3 "$SCRIPT_DIR/convert-mkdocs.py" "$TMPDIR/tags-index.md" "$VERSION_DIR/tags-index.md" + rm "$TMPDIR/tags-index.md" + fi + + rmdir "$TMPDIR" 2>/dev/null || true + + so=${SORT_ORDERS[$VERSION]:-50} + python3 "$SCRIPT_DIR/generate-version-yaml.py" "$VERSION" "$VERSION_DIR" "$so" > "$DATA_DIR/$VERSION.yml" + + echo " Done: $(find "$VERSION_DIR" -name '*.md' | wc -l) pages" +done + +for VERSION in 1.6.0 1.7.0; do + echo "--- Removing $VERSION (Jekyll era) ---" + rm -rf "$CONTENT_DIR/$VERSION" + rm -f "$DATA_DIR/$VERSION.yml" +done + +echo "" +echo "=== Done ===" +echo "Versions extracted: ${#MKDOCS_VERSIONS[@]}" +echo "Versions removed (Jekyll): 1.6.0, 1.7.0" diff --git a/documentation/docs-roq/generate-version-yaml.py b/documentation/docs-roq/generate-version-yaml.py new file mode 100644 index 000000000..0d30bd709 --- /dev/null +++ b/documentation/docs-roq/generate-version-yaml.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""Generate a version YAML file from extracted content.""" + +import os +import re +import sys + + +TUTORIAL_ORDER = [ + 'getting-mutiny', 'hello-mutiny', 'creating-uni-pipelines', + 'creating-multi-pipelines', 'observing-events', 'transforming-items', + 'transforming-items-asynchronously', 'handling-failures', 'retrying', + 'mutiny-workshop' +] + +TUTORIAL_ICONS = { + 'getting-mutiny': 'fa-solid fa-download', + 'hello-mutiny': 'fa-solid fa-hand-wave', + 'creating-uni-pipelines': 'fa-solid fa-code', + 'creating-multi-pipelines': 'fa-solid fa-code', + 'observing-events': 'fa-solid fa-eye', + 'transforming-items': 'fa-solid fa-shuffle', + 'transforming-items-asynchronously': 'fa-solid fa-shuffle', + 'handling-failures': 'fa-solid fa-triangle-exclamation', + 'retrying': 'fa-solid fa-rotate', + 'mutiny-workshop': 'fa-solid fa-flask', +} + + +def get_title(filepath): + """Extract title from first H1 in a markdown file.""" + try: + with open(filepath, 'r', encoding='utf-8') as f: + for line in f: + if line.startswith('# '): + return line[2:].strip() + except Exception: + pass + slug = os.path.splitext(os.path.basename(filepath))[0] + return slug.replace('-', ' ').title() + + +def escape_yaml(s): + """Escape a string for YAML double-quoted value.""" + return s.replace('\\', '\\\\').replace('"', '\\"') + + +def generate(version, version_dir, sort_order): + lines = [] + lines.append(f'label: "{version}"') + lines.append(f'path: "{version}"') + lines.append(f'sortOrder: {sort_order}') + lines.append('defaultVersion: false') + lines.append('devVersion: false') + lines.append('sections:') + + # Tutorials + tut_dir = os.path.join(version_dir, 'tutorials') + if os.path.isdir(tut_dir): + tut_files = [f for f in os.listdir(tut_dir) if f.endswith('.md')] + if tut_files: + lines.append(' - name: "Tutorials"') + lines.append(' items:') + # Ordered tutorials first + seen = set() + for slug in TUTORIAL_ORDER: + fname = slug + '.md' + if fname in tut_files: + seen.add(fname) + title = escape_yaml(get_title(os.path.join(tut_dir, fname))) + icon = TUTORIAL_ICONS.get(slug, 'fa-solid fa-file') + lines.append(f' - title: "{title}"') + lines.append(f' path: "/tutorials/{slug}"') + lines.append(f' icon: "{icon}"') + # Any remaining tutorials not in the order list + for fname in sorted(tut_files): + if fname not in seen: + slug = fname[:-3] + title = escape_yaml(get_title(os.path.join(tut_dir, fname))) + lines.append(f' - title: "{title}"') + lines.append(f' path: "/tutorials/{slug}"') + lines.append(f' icon: "fa-solid fa-file"') + + # Guides + guides_dir = os.path.join(version_dir, 'guides') + if os.path.isdir(guides_dir): + guide_files = sorted(f for f in os.listdir(guides_dir) if f.endswith('.md')) + if guide_files: + lines.append(' - name: "Guides"') + lines.append(' items:') + for fname in guide_files: + slug = fname[:-3] + title = escape_yaml(get_title(os.path.join(guides_dir, fname))) + lines.append(f' - title: "{title}"') + lines.append(f' path: "/guides/{slug}"') + lines.append(f' icon: "fa-solid fa-book"') + + # Reference + ref_dir = os.path.join(version_dir, 'reference') + if os.path.isdir(ref_dir): + ref_files = sorted(f for f in os.listdir(ref_dir) if f.endswith('.md')) + if ref_files: + lines.append(' - name: "Reference"') + lines.append(' items:') + for fname in ref_files: + slug = fname[:-3] + title = escape_yaml(get_title(os.path.join(ref_dir, fname))) + lines.append(f' - title: "{title}"') + lines.append(f' path: "/reference/{slug}"') + lines.append(f' icon: "fa-solid fa-file"') + lines.append(' - title: "API (Javadoc)"') + lines.append(' path: "https://javadoc.io/doc/io.smallrye.reactive/mutiny/latest/index.html"') + lines.append(' icon: "fa-solid fa-file-code"') + lines.append(' target: "_blank"') + + return '\n'.join(lines) + '\n' + + +if __name__ == '__main__': + version = sys.argv[1] + version_dir = sys.argv[2] + sort_order = int(sys.argv[3]) + print(generate(version, version_dir, sort_order), end='') diff --git a/documentation/docs-roq/pom.xml b/documentation/docs-roq/pom.xml new file mode 100644 index 000000000..093346660 --- /dev/null +++ b/documentation/docs-roq/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + io.smallrye.reactive + smallrye-mutiny-docs + 1.0-SNAPSHOT + + + 3.38.1 + 2.1.6 + 21 + 21 + UTF-8 + + + + + + io.quarkus + quarkus-bom + ${quarkus.version} + pom + import + + + + + + + io.quarkiverse.roq + quarkus-roq + ${roq.version} + + + io.quarkiverse.roq + quarkus-roq-theme-default + ${roq.version} + + + io.quarkiverse.roq + quarkus-roq-plugin-markdown + ${roq.version} + + + io.quarkiverse.roq + quarkus-roq-plugin-diagram + ${roq.version} + + + io.quarkiverse.roq + quarkus-roq-plugin-lunr + ${roq.version} + + + io.quarkiverse.roq + quarkus-roq-plugin-tagging + ${roq.version} + + + io.quarkiverse.roq + quarkus-roq-plugin-sitemap + ${roq.version} + + + io.quarkus + quarkus-arc + + + org.mvnpm + highlight.js + 11.11.1 + provided + + + org.mvnpm + highlightjs-copy + 1.0.6 + provided + + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + true + + + + build + + + + + + + diff --git a/documentation/docs-roq/public/images/CF_logo_horizontal_single_reverse.svg b/documentation/docs-roq/public/images/CF_logo_horizontal_single_reverse.svg new file mode 100644 index 000000000..2b8907471 --- /dev/null +++ b/documentation/docs-roq/public/images/CF_logo_horizontal_single_reverse.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/docs-roq/public/images/distributed_systems_are_asynchronous.png b/documentation/docs-roq/public/images/distributed_systems_are_asynchronous.png new file mode 100644 index 000000000..0d00a8c9e Binary files /dev/null and b/documentation/docs-roq/public/images/distributed_systems_are_asynchronous.png differ diff --git a/documentation/docs-roq/public/images/favicon.ico b/documentation/docs-roq/public/images/favicon.ico new file mode 100644 index 000000000..b3ca4943c Binary files /dev/null and b/documentation/docs-roq/public/images/favicon.ico differ diff --git a/documentation/docs-roq/public/images/logo-white.png b/documentation/docs-roq/public/images/logo-white.png new file mode 100644 index 000000000..4472249bb Binary files /dev/null and b/documentation/docs-roq/public/images/logo-white.png differ diff --git a/documentation/docs-roq/public/images/logo-white.svg b/documentation/docs-roq/public/images/logo-white.svg new file mode 100644 index 000000000..fa27cb24f --- /dev/null +++ b/documentation/docs-roq/public/images/logo-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/documentation/docs-roq/public/images/logo.png b/documentation/docs-roq/public/images/logo.png new file mode 100644 index 000000000..f8e4ca32a Binary files /dev/null and b/documentation/docs-roq/public/images/logo.png differ diff --git a/documentation/docs-roq/public/images/running-workshop-sample.png b/documentation/docs-roq/public/images/running-workshop-sample.png new file mode 100644 index 000000000..6eec15382 Binary files /dev/null and b/documentation/docs-roq/public/images/running-workshop-sample.png differ diff --git a/documentation/docs-roq/src/main/java/docs/Attributes.java b/documentation/docs-roq/src/main/java/docs/Attributes.java new file mode 100644 index 000000000..9bc29f986 --- /dev/null +++ b/documentation/docs-roq/src/main/java/docs/Attributes.java @@ -0,0 +1,12 @@ +package docs; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.quarkiverse.roq.data.runtime.annotations.DataMapping; + +@DataMapping(value = "attributes") +public record Attributes(@JsonProperty("project-version") String projectVersion, Versions versions) { + + public record Versions(String mutiny, @JsonProperty("vertx_bindings") String vertxBindings) { + } +} diff --git a/documentation/docs-roq/src/main/java/docs/SnippetExtension.java b/documentation/docs-roq/src/main/java/docs/SnippetExtension.java new file mode 100644 index 000000000..72419859e --- /dev/null +++ b/documentation/docs-roq/src/main/java/docs/SnippetExtension.java @@ -0,0 +1,82 @@ +package docs; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Collectors; + +import io.quarkus.qute.TemplateExtension; + +@TemplateExtension(namespace = "snippet") +public class SnippetExtension { + + private static final Path SNIPPET_ROOT = Path.of("../src/test"); + + static String insert(String file) throws IOException { + return insert(file, null); + } + + static String insert(String file, String tag) throws IOException { + Path path = SNIPPET_ROOT.resolve(file); + if (!Files.exists(path)) { + return ""; + } + if (tag == null || tag.isBlank()) { + return Files.readString(path); + } + boolean[] recording = { false }; + String content = Files.readAllLines(path).stream() + .filter(line -> { + if (!recording[0] && line.contains("<" + tag + ">")) { + recording[0] = true; + return false; + } else if (recording[0] && line.contains("")) { + recording[0] = false; + return false; + } + return recording[0]; + }) + .collect(Collectors.joining("\n")); + if (content.isEmpty()) { + return ""; + } + return dedent(content); + } + + private static String dedent(String text) { + String[] lines = text.split("\n", -1); + int minIndent = Integer.MAX_VALUE; + for (String line : lines) { + if (line.isBlank()) { + continue; + } + int indent = 0; + for (char c : line.toCharArray()) { + if (c == ' ') { + indent++; + } else if (c == '\t') { + indent += 4; + } else { + break; + } + } + minIndent = Math.min(minIndent, indent); + } + if (minIndent == 0 || minIndent == Integer.MAX_VALUE) { + return text; + } + int finalMinIndent = minIndent; + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + if (line.isBlank()) { + sb.append("\n"); + } else { + sb.append(line.substring(Math.min(finalMinIndent, line.length()))).append("\n"); + } + } + if (!sb.isEmpty() && sb.charAt(sb.length() - 1) == '\n') { + sb.setLength(sb.length() - 1); + } + return sb.toString(); + } +} diff --git a/documentation/docs-roq/src/main/java/docs/Versions.java b/documentation/docs-roq/src/main/java/docs/Versions.java new file mode 100644 index 000000000..9dbfcfc2d --- /dev/null +++ b/documentation/docs-roq/src/main/java/docs/Versions.java @@ -0,0 +1,37 @@ +package docs; + +import io.quarkiverse.roq.data.runtime.annotations.DataMapping; + +import java.util.Comparator; +import java.util.List; + +@DataMapping(value = "versions", type = DataMapping.Type.ARRAY_DIR) +public record Versions(List list) { + + public List sorted() { + return list.stream() + .sorted(Comparator.comparingInt(Version::sortOrder)) + .toList(); + } + + public record Version( + String label, + String path, + int sortOrder, + boolean defaultVersion, + boolean devVersion, + List
sections) { + + public record Section( + String name, + List items) { + } + + public record MenuItem( + String title, + String path, + String icon, + String target) { + } + } +} diff --git a/documentation/docs-roq/src/main/resources/application.properties b/documentation/docs-roq/src/main/resources/application.properties new file mode 100644 index 000000000..5eee5be20 --- /dev/null +++ b/documentation/docs-roq/src/main/resources/application.properties @@ -0,0 +1,9 @@ +# Site identity +site.url=https://smallrye.io/smallrye-mutiny/ + +# Use {=expr} for Qute expressions, bare {stuff} is literal text +quarkus.qute.alt-expr-syntax=true + +# Lenient rendering during migration +quarkus.qute.strict-rendering=false +quarkus.qute.property-not-found-strategy=NOOP diff --git a/documentation/docs-roq/src/main/resources/templates/layouts/page.html b/documentation/docs-roq/src/main/resources/templates/layouts/page.html new file mode 100644 index 000000000..2dd083319 --- /dev/null +++ b/documentation/docs-roq/src/main/resources/templates/layouts/page.html @@ -0,0 +1,5 @@ +--- +theme-layout: page +link: /:raw-path +--- +{#insert /} diff --git a/documentation/docs-roq/src/main/resources/templates/partials/roq-default/head-scripts.html b/documentation/docs-roq/src/main/resources/templates/partials/roq-default/head-scripts.html new file mode 100644 index 000000000..6993caff3 --- /dev/null +++ b/documentation/docs-roq/src/main/resources/templates/partials/roq-default/head-scripts.html @@ -0,0 +1,212 @@ +{@java.lang.String tag} + + + + + +{#bundle /} +{#search-script /} + + diff --git a/documentation/docs-roq/src/main/resources/templates/partials/roq-default/sidebar-menu.html b/documentation/docs-roq/src/main/resources/templates/partials/roq-default/sidebar-menu.html new file mode 100644 index 000000000..14ada28c9 --- /dev/null +++ b/documentation/docs-roq/src/main/resources/templates/partials/roq-default/sidebar-menu.html @@ -0,0 +1,27 @@ +{@io.quarkiverse.roq.frontmatter.runtime.model.Site site} +{@java.util.List menu} + +{#let versions=cdi:versions} +{#search-overlay /} + +{/let} diff --git a/documentation/docs-roq/src/main/resources/templates/partials/top-nav.html b/documentation/docs-roq/src/main/resources/templates/partials/top-nav.html new file mode 100644 index 000000000..3e8540220 --- /dev/null +++ b/documentation/docs-roq/src/main/resources/templates/partials/top-nav.html @@ -0,0 +1,38 @@ +{@io.quarkiverse.roq.frontmatter.runtime.model.Site site} +{@java.util.List menu} + +{#let versions=cdi:versions} + +{/let} diff --git a/documentation/docs-roq/src/main/resources/templates/partials/version-bar.html b/documentation/docs-roq/src/main/resources/templates/partials/version-bar.html new file mode 100644 index 000000000..afda44da5 --- /dev/null +++ b/documentation/docs-roq/src/main/resources/templates/partials/version-bar.html @@ -0,0 +1,68 @@ +{@io.quarkiverse.roq.frontmatter.runtime.model.Site site} +{#let versions=cdi:versions} + +{/let} diff --git a/documentation/docs-roq/src/main/resources/templates/theme-layouts/roq-default/home.html b/documentation/docs-roq/src/main/resources/templates/theme-layouts/roq-default/home.html new file mode 100644 index 000000000..c5be4466e --- /dev/null +++ b/documentation/docs-roq/src/main/resources/templates/theme-layouts/roq-default/home.html @@ -0,0 +1,22 @@ +{#include roq-templates/theme-layouts/roq-default/default} + +{@io.quarkiverse.roq.frontmatter.runtime.model.Site site} + +{! Site header: logo + version selector !} +{#include partials/version-bar.html /} + +{! Top navigation tabs !} +{#include partials/top-nav.html menu=cdi:menu.items /} + +{! Search overlay (needed on homepage since there's no sidebar) !} +{#search-overlay /} + +{! Homepage: no sidebar, full-width content !} +{#include partials/roq-default/sidebar-darkmode /} +
+
+ {#insert /} +
+
+ +{/include} diff --git a/documentation/docs-roq/src/main/resources/templates/theme-layouts/roq-default/main.html b/documentation/docs-roq/src/main/resources/templates/theme-layouts/roq-default/main.html new file mode 100644 index 000000000..dc30a28ed --- /dev/null +++ b/documentation/docs-roq/src/main/resources/templates/theme-layouts/roq-default/main.html @@ -0,0 +1,23 @@ +{#include roq-templates/theme-layouts/roq-default/default} + +{@io.quarkiverse.roq.frontmatter.runtime.model.Site site} + +{! Site header: logo + version selector !} +{#insert version-bar}{#include partials/version-bar.html /}{/} + +{! Top navigation tabs !} +{#insert top-nav}{#include partials/top-nav.html menu=cdi:menu.items /}{/} + + +{#insert darkmode}{#include partials/roq-default/sidebar-darkmode /}{/} +
+ {#insert /} +
+ +{/include} diff --git a/documentation/docs-roq/src/main/resources/web/app/main.js b/documentation/docs-roq/src/main/resources/web/app/main.js new file mode 100644 index 000000000..756627522 --- /dev/null +++ b/documentation/docs-roq/src/main/resources/web/app/main.js @@ -0,0 +1,7 @@ +import hljs from 'highlight.js'; +import CopyButtonPlugin from 'highlightjs-copy'; +import 'highlightjs-copy/dist/highlightjs-copy.min.css'; +import 'highlight.js/styles/github.css'; + +hljs.addPlugin(new CopyButtonPlugin()); +hljs.highlightAll(); diff --git a/documentation/docs-roq/web/_custom.css b/documentation/docs-roq/web/_custom.css new file mode 100644 index 000000000..27e2cb185 --- /dev/null +++ b/documentation/docs-roq/web/_custom.css @@ -0,0 +1,551 @@ +/* Theme customization: https://iamroq.dev/theme/default/#css-customization */ +/* Color palette: Material Design Brown (primary) + Deep Orange (accent) */ +/* Matches mkdocs.yml: primary=brown, accent=deep orange, font=IBM Plex */ + +@theme inline { + /* Accent (brand): Material Brown — sidebar, headers, navigation */ + --color-accent-50: #efebe9; + --color-accent-100: #d7ccc8; + --color-accent-200: #bcaaa4; + --color-accent-300: #a1887f; + --color-accent-400: #8d6e63; + --color-accent-500: #795548; + --color-accent-600: #6d4c41; + --color-accent-700: #5d4037; + --color-accent-800: #4e342e; + --color-accent-900: #3e2723; + --color-accent-950: #1b0000; + + /* Pop (interactive): Material Deep Orange — links, buttons, highlights */ + --color-pop-50: #fbe9e7; + --color-pop-100: #ffccbc; + --color-pop-200: #ffab91; + --color-pop-300: #ff8a65; + --color-pop-400: #ff7043; + --color-pop-500: #ff5722; + --color-pop-600: #f4511e; + --color-pop-700: #e64a19; + --color-pop-800: #d84315; + --color-pop-900: #bf360c; + --color-pop-950: #870000; + + /* Typography: IBM Plex (from mkdocs.yml theme.font) */ + --font-body: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif; + --font-heading: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif; +} + +/* Sidebar: brown gradient (light), near-black (dark — mkdocs primary: black) */ +:root { + --sidebar-bg: linear-gradient(180deg, #795548 0%, #4e342e 100%); + --site-header-h: 2.75rem; + --top-nav-h: 2.5rem; + --header-total-h: calc(var(--site-header-h) + var(--top-nav-h)); +} + +@media (prefers-color-scheme: dark) { + :root { + --sidebar-bg: linear-gradient(180deg, #1a1a1a 0%, #0d0d0d 100%); + } +} + +/* Hero logo sizing */ +.roq-hero-logo { + @apply w-[200px]; +} + +/* ── Site header (logo + version selector) ─────────────────────────────── */ + +.site-header { + position: fixed; + top: 0; + left: 0; + right: 0; + height: var(--site-header-h); + background: #795548; + color: #fff; + z-index: 100; +} + +@media (prefers-color-scheme: dark) { + .site-header { + background: #1a1a1a; + } +} + +.site-header-inner { + display: flex; + align-items: center; + justify-content: space-between; + height: 100%; + padding: 0 1rem; +} + +.site-header-logo { + display: flex; + align-items: center; + gap: 0.5rem; + text-decoration: none; + color: #fff; + flex-shrink: 0; +} + +.site-header-logo img { + height: 1.75rem; + width: auto; +} + +.site-header-title { + font-weight: 700; + font-size: 1.1rem; + letter-spacing: 0.02em; +} + +.site-header-right { + display: flex; + align-items: center; + gap: 0.75rem; +} + +/* Header search button */ + +.header-search-btn { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.3rem 0.65rem; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 0.375rem; + color: rgba(255, 255, 255, 0.75); + font-size: 0.8rem; + cursor: pointer; + transition: background 0.15s ease, color 0.15s ease; +} + +.header-search-btn:hover { + background: rgba(255, 255, 255, 0.15); + color: #fff; +} + +.header-search-btn i { + font-size: 0.75rem; +} + +.header-search-label { + font-size: 0.8rem; +} + +.header-search-kbd { + font-size: 0.65rem; + padding: 0.1rem 0.3rem; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 0.2rem; + opacity: 0.6; + font-family: inherit; +} + +@media (max-width: 640px) { + .header-search-label, + .header-search-kbd { + display: none; + } +} + +/* Header resource links (GitHub, SmallRye) */ + +.header-resources { + display: flex; + align-items: center; + gap: 0.25rem; +} + +.header-resource-link { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + color: rgba(255, 255, 255, 0.7); + text-decoration: none; + border-radius: 0.25rem; + transition: color 0.15s ease, background 0.15s ease; + font-size: 1.1rem; +} + +.header-resource-link:hover { + color: #fff; + background: rgba(255, 255, 255, 0.1); +} + +/* Hide the about section in the sidebar (logo is in the header now) */ +.sidebar.main .about { + display: none; +} + +/* ── Top navigation bar (section tabs) ──────────────────────────────────── */ + +.top-nav { + position: fixed; + top: var(--site-header-h); + left: 0; + right: 0; + height: var(--top-nav-h); + background: #6d4c41; + border-bottom: 1px solid rgba(0, 0, 0, 0.15); + z-index: 99; +} + +@media (prefers-color-scheme: dark) { + .top-nav { + background: #141414; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + } +} + +.top-nav-inner { + display: flex; + align-items: center; + height: 100%; + gap: 0; + padding: 0 1rem; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; +} + +.top-nav-inner::-webkit-scrollbar { + display: none; +} + +.top-nav-tab { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0 1rem; + height: 100%; + color: rgba(255, 255, 255, 0.75); + text-decoration: none; + font-size: 0.85rem; + font-weight: 500; + white-space: nowrap; + border-bottom: 2px solid transparent; + transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease; +} + +.top-nav-tab:hover { + color: #fff; + background: rgba(255, 255, 255, 0.06); +} + +.top-nav-tab.top-nav-active { + color: #fff; + border-bottom-color: #ff5722; +} + +.top-nav-tab i { + font-size: 0.8rem; + opacity: 0.8; +} + +/* ── Sidebar: push below fixed header ──────────────────────────────────── */ + +@media (min-width: 768px) { + .sidebar.main { + top: var(--header-total-h) !important; + height: calc(100vh - var(--header-total-h)) !important; + } +} + +/* ── Main content: push below fixed header ─────────────────────────────── */ + +.main-content { + padding-top: var(--header-total-h); +} + +/* ── Sidebar active link ────────────────────────────────────────────────── */ + +.sidebar-active > a { + font-weight: 600; + color: #ff5722 !important; +} + +@media (prefers-color-scheme: dark) { + .sidebar-active > a { + color: #ff7043 !important; + } +} + +/* ── Homepage: full-width, no sidebar ────────────────────────────────────── */ + +.home-no-sidebar { + margin-left: 0 !important; + width: 100% !important; + max-width: 100% !important; +} + +/* ── Version dropdown ────────────────────────────────────────────────────── */ + +.version-dropdown { + position: relative; +} + +.version-dropdown-toggle { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.3rem 0.65rem; + background: rgba(255, 255, 255, 0.12); + border: 1px solid rgba(255, 255, 255, 0.25); + border-radius: 0.375rem; + color: inherit; + font-size: 0.85rem; + cursor: pointer; + transition: background 0.15s ease; +} + +.version-dropdown-toggle:hover { + background: rgba(255, 255, 255, 0.2); +} + +.version-dropdown-chevron { + font-size: 0.6rem; + transition: transform 0.2s ease; +} + +.version-dropdown-menu { + display: none; + position: absolute; + top: 100%; + right: 0; + min-width: 200px; + z-index: 200; + margin-top: 0.25rem; + padding: 0.25rem 0; + background: #4e342e; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 0.375rem; + list-style: none; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + max-height: 20rem; + overflow-y: auto; +} + +@media (prefers-color-scheme: dark) { + .version-dropdown-menu { + background: #2a2a2a; + } +} + +.version-dropdown-menu.version-dropdown-open { + display: block; +} + +.version-dropdown-menu li { + padding: 0.4rem 0.75rem; + font-size: 0.85rem; + cursor: pointer; + color: #fff; + transition: background 0.1s ease; +} + +.version-dropdown-menu li:hover { + background: rgba(255, 255, 255, 0.1); +} + +.version-dropdown-menu li.version-current { + font-weight: 600; + background: rgba(255, 255, 255, 0.08); +} + +.version-badge { + display: inline-block; + font-size: 0.7rem; + padding: 0.1rem 0.35rem; + border-radius: 0.25rem; + background: rgba(255, 255, 255, 0.2); + margin-left: 0.35rem; + vertical-align: middle; +} + +.version-badge-dev { + background: rgba(255, 193, 7, 0.3); + color: #ffd54f; +} + +/* ── Dev banner ──────────────────────────────────────────────────────────── */ + +.dev-banner { + padding: 0.4rem 1.5rem; + background: rgba(255, 193, 7, 0.15); + border-top: 1px solid rgba(255, 193, 7, 0.3); + font-size: 0.8rem; + color: #ffd54f; +} + +.dev-banner a { + color: #fff; + text-decoration: underline; +} + +.dev-banner i { + margin-right: 0.35rem; +} + +/* ── Search version toggle ──────────────────────────────────────────────── */ + +.search-version-toggle { + display: flex; + align-items: center; + gap: 0.4rem; + margin-top: 0.5rem; + font-size: 0.8rem; + opacity: 0.8; +} + +.search-version-toggle input[type="checkbox"] { + accent-color: #ff5722; +} + +@media (width >= 48rem) { + .sidebar.main:has(.search-overlay.active) { + z-index: 10000; + } +} + +.hljs-copy-wrapper { + transform: none; +} + +/* ── Content column width ─────────────────────────────────────────────────── */ + +.page-content .content-main { + max-width: var(--container-5xl); +} + +/* ── Tables ──────────────────────────────────────────────────────────────── */ + +.page-content table, +.roq-section table { + width: 100%; + border-collapse: collapse; + margin: 1.5rem 0; +} + +.page-content th, +.page-content td, +.roq-section th, +.roq-section td { + padding: 0.6rem 1rem; + text-align: left; + border: 1px solid rgba(128, 128, 128, 0.3); +} + +.page-content th, +.roq-section th { + font-weight: 600; + background: rgba(121, 85, 72, 0.1); +} + +.page-content tr:nth-child(even), +.roq-section tr:nth-child(even) { + background: rgba(121, 85, 72, 0.04); +} + +/* ── Syntax Highlighting (highlight.js) ───────────────────────────────────── */ + +.hljs, +pre code.hljs { + font-family: 'IBM Plex Mono', ui-monospace, monospace; + font-size: 0.875rem; + line-height: 1.6; +} + +.roq-section pre { + text-align: left; +} + +/* ── Homepage (matches MkDocs Material layout) ───────────────────────────── */ + +.mutiny-home { + max-width: 52rem; + margin: 0 auto; + padding: 2rem 1.5rem 3rem; +} + +.mutiny-home h1 { + font-size: 1.6rem; + font-weight: 700; + margin-bottom: 1.5rem; + line-height: 1.3; +} + +.mutiny-home pre { + text-align: left; + margin-bottom: 1.5rem; + border-radius: 0.375rem; + overflow-x: auto; +} + +.mutiny-cta { + display: inline-block; + padding: 0.6rem 1.4rem; + background: #ff5722; + color: #fff !important; + text-decoration: none; + border-radius: 0.375rem; + font-weight: 600; + font-size: 0.95rem; + transition: background 0.15s ease; + margin-bottom: 2rem; +} + +.mutiny-cta:hover { + background: #e64a19; +} + +.mutiny-features { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); + gap: 1.5rem; + margin-top: 2rem; +} + +.mutiny-feature h2 { + font-size: 1.15rem; + font-weight: 600; + margin-bottom: 0.5rem; +} + +.mutiny-feature p { + font-size: 0.92rem; + line-height: 1.6; + margin-bottom: 0.5rem; +} + +.mutiny-footer { + margin-top: 3rem; + padding-top: 1.5rem; + border-top: 1px solid rgba(128, 128, 128, 0.2); + text-align: center; + font-size: 0.8rem; + opacity: 0.8; +} + +.mutiny-footer-cf { + margin-bottom: 0.75rem; +} + +.cf-logo { + height: 2rem; + opacity: 0.7; +} + +.mutiny-footer a { + text-decoration: underline; +} + +/* ── Dark mode toggle: below fixed header ──────────────────────────────── */ + +.dark-mode-toggle { + top: calc(var(--header-total-h) + 0.75rem) !important; +}