Skip to content

Commit

Permalink
Merge pull request #400 from smallrye/call-vs-invoke-getting-started
Browse files Browse the repository at this point in the history
Write a getting started guide about events, invoke and call
  • Loading branch information
jponge authored Dec 16, 2020
2 parents be964de + 69d9e7b commit 49722c8
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 4 deletions.
1 change: 1 addition & 0 deletions documentation/src/main/jekyll/_data/categories.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- first-lines-of-code
- creating-unis
- creating-multis
- observing-events
- transforming-items
- transforming-items-async
- handling-failures
Expand Down
5 changes: 5 additions & 0 deletions documentation/src/main/jekyll/_data/guides.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ creating-multis:
text: Learn how to create Multi instances.
url: getting-started/creating-multis

observing-events:
title: Observing events
text: Learn how to observes the events emitted by Uni and Multi instances.
url: getting-started/observing-events

transforming-items:
title: Transforming items
text: Learn how to (synchronously) transform received item
Expand Down
3 changes: 0 additions & 3 deletions documentation/src/main/jekyll/_sass/asciidoc.scss
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,6 @@ table.tableblock tbody tr td {
padding: 0.5rem
}

table.tableblock tbody tr td p {
font-size: 0.9rem
}

table.configuration-reference a {
text-decoration: none;
Expand Down
23 changes: 23 additions & 0 deletions documentation/src/main/jekyll/_sass/table.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
table {
width: 100%;
text-align: left;
border-collapse: collapse;
margin: 0 0 1.25rem 0;
border-spacing: 2px;
border-color: $border-color;
}

table th {
font-family: "Montserrat",sans-serif;
text-transform: uppercase;
font-weight: 500;
font-size: large;
background-color: $color-gray-100;
border-color: $border-color;
}

table th, table td {
border: 2px solid $border-color;
vertical-align: middle;
padding: .625rem .9375rem;
}
3 changes: 2 additions & 1 deletion documentation/src/main/jekyll/assets/css/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ $baseurl: "{{ site.baseurl }}";
@import "page";
@import "admonition";
@import "hero";
@import "code";
@import "code";
@import "table";
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
115 changes: 115 additions & 0 deletions documentation/src/main/jekyll/getting-started/observing-events.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
:page-layout: getting-started
:page-guide-id: observing-events
:page-liquid:
:include_dir: ../../../../src/test/java/guides

== 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:

[cols="15,15,25,30", options="header", stripes=even]
|===
|Event |Uni / Multi | Direction | Note

|**item** | Uni and Multi | upstream -> downstream | The upstream sent an item.
|**failure** | Uni and Multi | upstream -> downstream | The upstream failed.
|**completion** | Multi only | 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 | Even 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 event
|**overflow** | Multi only | upstream -> downstream | The upstream has emitted more than the downstream can handle
|**request** | Multi only | 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:

[source, java, indent=0]
----
include::{include_dir}/ObserveTest.java[tag=invoke]
----

As said above, `invoke` is synchronous.
Mutiny invokes the callback and propagates the event downstream when the callback returns.
It blocks the dispatching.

[role=reduce]
image::event-invoke.png[]

Of course, we highly recommend you not to block.

The following snippets show how you can log the different types of events.


[source, java, indent=0]
----
include::{include_dir}/ObserveTest.java[tag=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.

[role=reduce]
image::event-call.png[]

Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item:

[source, java, indent=0]
----
include::{include_dir}/ObserveTest.java[tag=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:

[source, java, indent=0]
----
include::{include_dir}/ObserveTest.java[tag=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:

[cols="30, 30, 30", options="header"]
|===
| | `invoke` | `call`
| **Nature** | synchronous | asynchronous
| **Return type** | `void`| `Uni<?>`
| **Main Use cases** | logging, synchronous side-effect | closing resources, flushing data
|===

58 changes: 58 additions & 0 deletions documentation/src/test/java/guides/ObserveTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package guides;

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import org.junit.jupiter.api.Test;

import java.time.Duration;

public class ObserveTest {

@Test
public void test() {
Uni<String> uni = Uni.createFrom().item("hello");
Multi<String> multi = Multi.createFrom().items("a", "b", "c");
// tag::invoke[]
Uni<String> u = uni.onItem()
.invoke(i -> System.out.println("Received item: " + i));
Multi<String> m = multi.onItem()
.invoke(i -> System.out.println("Received item: " + i));
// end::invoke[]

// tag::call[]
multi
.onItem().call(i ->
Uni.createFrom().voidItem()
.onItem().delayIt().by(Duration.ofSeconds(1)
)
);
// end::call[]

MyResource resource = new MyResource();
// tag::close[]
multi
.onCompletion().call(() -> resource.close());
// end::close[]
}

@Test
public void all() {
Multi<String> multi = Multi.createFrom().items("a", "b", "c");
// tag::invoke-all[]
multi
.onSubscribe().invoke(() -> System.out.println("⬇️ Subscribed"))
.onItem().invoke(i -> System.out.println("⬇️ Received item: " + i))
.onFailure().invoke(f -> System.out.println("⬇️ Failed with " + f))
.onCompletion().invoke(() -> System.out.println("⬇️ Completed"))
.onCancellation().invoke(() -> System.out.println("⬆️ Cancelled"))
.onRequest().invoke(l -> System.out.println("⬆️ Requested: " + l));
// end::invoke-all[]

}

static class MyResource {
Uni<Void> close() {
return Uni.createFrom().voidItem();
}
}
}

0 comments on commit 49722c8

Please sign in to comment.