mp.reactivemessaging.introduction.adoc Maven / Gradle / Ivy
The newest version!
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2020, 2024 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
///////////////////////////////////////////////////////////////////////////////
= Reactive Messaging MP
:spec-name: MicroProfile Reactive Messaging
:description: {spec-name} support in Helidon MP
:keywords: helidon, mp, microprofile, messaging
:messaging-release: {version.lib.microprofile-reactive-messaging-api}
:feature-name: MicroProfile Reactive Messaging
:microprofile-bundle: false
:rootdir: {docdir}/../..
include::{rootdir}/includes/mp.adoc[]
== Contents
- <>
- <>
- <>
- <>
- <>
== Overview
Reactive messaging offers a new way of processing messages that is different from the older method of using message-driven beans. One significant difference is that blocking is no longer the only way to apply backpressure to the message source.
Reactive messaging uses reactive streams as message channels so you can construct very effective pipelines for working
with the messages or, if you prefer, you can continue to use older messaging methods. Like the message-driven beans,
link:{microprofile-reactive-messaging-spec-url}[MicroProfile Reactive Messaging]
uses CDI beans to produce, consume or process messages over Reactive Streams.
These messaging beans are expected to be either `ApplicationScoped` or `Dependent` scoped.
Messages are managed by methods annotated by `@Incoming` and `@Outgoing`
and the invocation is always driven by message core - either at assembly time, or for every message coming from the stream.
include::{rootdir}/includes/mp.adoc[]
include::{rootdir}/includes/dependencies.adoc[]
[source,xml]
----
io.helidon.microprofile.messaging
helidon-microprofile-messaging
----
To include health checks for Messaging add the following dependency:
[source,xml]
----
io.helidon.microprofile.messaging
helidon-microprofile-messaging-health
----
== Usage
- <>
- <>
- <>
- <>
- <>
- <>
=== Channels
Reactive messaging uses named channels to connect one source (upstream) with one consumer (downstream).
Each channel needs to have both ends connected otherwise the container cannot successfully start.
image::msg/channel.svg[Messaging Channel]
Channels can be connected either to <> (1), <> (2) or <> (3) on the upstream side. And <> (4), <> (5) or <> (6)
on the downstream.
==== Consuming Method
Consuming methods can be connected to the channel's downstream to consume the message coming through the channel.
The incoming annotation has one required attribute `value` that defines the channel name.
Consuming method can function in two ways:
* consume every message coming from the stream connected to the <> - invoked per each message
* prepare reactive stream's subscriber and connect it to the channel - invoked only once during the channel construction
[source,java]
.Example consuming every message from channel `example-channel-2`:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_1, indent=0]
----
[source,java]
.Example preparing reactive stream subscriber for channel `example-channel-1`:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_2, indent=0]
----
==== Injected Publisher
Directly injected publisher can be connected as a channel downstream,
you can consume the data from the channel by subscribing to it.
Helidon can inject following types of publishers:
* `Publisher` - Reactive streams publisher with unwrapped payload
* `Publisher>` - Reactive streams publisher with whole message
* `PublisherBuilder` - MP Reactive streams operators publisher builder with unwrapped payload
* `PublisherBuilder>` - MP Reactive streams operators publisher builder with whole message
* `Flow.Publisher` - JDK's flow publisher with unwrapped payload
* `Flow.Publisher>` - JDK's flow publisher with whole message
* `Multi` - Helidon flow reactive operators with unwrapped payload
* `Multi>` - Helidon flow reactive operators with whole message
[source,java]
.Example of consuming payloads from channel `example-channel-1` with injected publisher:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_3, indent=0]
----
==== Producing Method
The annotation has one required attribute `value` that defines the
link:{microprofile-reactive-messaging-spec-url}#_channel[channel] name.
The annotated <> can function in two ways:
* produce exactly one message to the stream connected to the link:{microprofile-reactive-messaging-spec-url}#_channel[channel]
* prepare reactive stream's publisher and connect it to the link:{microprofile-reactive-messaging-spec-url}#_channel[channel]
[source,java]
.Example producing exactly one message to channel `example-channel-1`:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_4, indent=0]
----
[source,java]
.Example preparing reactive stream publisher publishing three messages to the channel `example-channel-1`:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_5, indent=0]
----
WARNING: Messaging methods are not meant to be invoked directly!
=== Emitter
To send messages from imperative code, you can inject a special channel source called an emitter.
Emitter can serve only as an upstream, source of the messages, for messaging channel.
[source,java]
.Example of sending message from JAX-RS method to channel `example-channel-1`
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_6, indent=0]
----
Emitters, as a source of messages for reactive channels, need to address possible backpressure from the downstream side
of the channel. In case there is not enough demand from the downstream, you can configure a buffer size strategy using the `@OnOverflow` annotation. Additional overflow strategies are described below.
.Overflow strategies
|===
|Strategy |Description
|BUFFER |Buffer unconsumed values until configured bufferSize is reached, when reached calling `Emitter.emit` throws `IllegalStateException`. Buffer size can be configured with `@OnOverflow` or with config key `mp.messaging.emitter.default-buffer-size`. Default value is `128`.
|UNBOUNDED_BUFFER |Buffer unconsumed values until application runs out of memory.
|THROW_EXCEPTION |Calling `Emitter.emit` throws `IllegalStateException` if there is not enough items requested by downstream.
|DROP |If there is not enough items requested by downstream, emitted message is silently dropped.
|FAIL |If there is not enough items requested by downstream, emitting message causes error signal being send to downstream. Whole channel is terminated. No other messages can be sent.
|LATEST |Keeps only the latest item. Any previous unconsumed message is silently dropped.
|NONE |Messages are sent to downstream even if there is no demand. Backpressure is effectively ignored.
|===
==== Processing Method
Such link:{microprofile-reactive-messaging-spec-url}#_method_consuming_and_producing[methods]
acts as processors, consuming messages from one channel and producing to another.
image::msg/processor.svg[Processor method connecting two channels together]
Diagram shows how processing method (2) serves as a downstream to the `my-channel` (1) and an upstream to the `other-channel` (3),
connecting them together.
Processing method can function in multiple ways:
* process every message
* prepare reactive stream's processor and connect it between the channels
* on every message prepare new publisher(equivalent to `flatMap` operator)
[source,java]
.Example processing every message from channel `example-channel-1` to channel `example-channel-2`:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_7, indent=0]
----
[source,java]
.Example preparing processor stream to be connected between channels `example-channel-1` and `example-channel-2`:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_8, indent=0]
----
[source,java]
.Example processing every message from channel `example-channel-1` as stream to be flattened to channel `example-channel-2`:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_9, indent=0]
----
=== Connector
Messaging connector is an application-scoped bean that implements one or both of following interfaces:
* `IncomingConnectorFactory` - connector can create an upstream publisher to produce messages to a channel
* `OutgoingConnectorFactory` - connector can create a downstream subscriber to consume messages from a channel
[source,java]
.Example connector `example-connector`:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_10, indent=0]
----
=== Message
The Reactive Messaging link:{microprofile-reactive-messaging-spec-url}#_message[Message]
class can be used to wrap or unwrap data items between methods and connectors.
The message wrapping and unwrapping can be performed explicitly by using
`org.eclipse.microprofile.reactive.messaging.Message#of(T)` or implicitly through the messaging core.
[source,java]
.Example of explicit and implicit wrapping and unwrapping
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_11, indent=0]
----
=== Acknowledgement
Messages carry a callback for reception acknowledgement (ack) and negative acknowledgement (nack). An acknowledgement in messaging methods is possible manually by
`org.eclipse.microprofile.reactive.messaging.Message#ack` or automatically according explicit
or implicit acknowledgement strategy by the messaging core. Explicit strategy configuration is possible
with `@Acknowledgment` annotation which has one required attribute `value` that expects the strategy type from enum
`org.eclipse.microprofile.reactive.messaging.Acknowledgment.Strategy`. More information about supported signatures
and implicit automatic acknowledgement can be found in specification
link:{microprofile-reactive-messaging-spec-url}#_message_acknowledgement[Message acknowledgement].
[[terms]]
.Acknowledgement strategies
|===
|`@Acknowledgment(Acknowledgment.Strategy.NONE)`| No acknowledgment
|`@Acknowledgment(Acknowledgment.Strategy.MANUAL)`| No automatic acknowledgment
|`@Acknowledgment(Acknowledgment.Strategy.PRE_PROCESSING)`| Ack automatically before method invocation or processing
|`@Acknowledgment(Acknowledgment.Strategy.POST_PROCESSING)`| Ack automatically after method invocation or processing
|===
[source,java]
.Example of manual acknowledgment
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_12, indent=0]
----
<1> Calling ack() will print "This particular message was acked!" to System.out
[source,java]
.Example of manual acknowledgment
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_13, indent=0]
----
<1> Calling ack() will print "This particular message was acked!" to System.out
[source,java]
.Example of explicit pre-process acknowledgment
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_14, indent=0]
----
[source,java]
.Example of explicit post-process acknowledgment
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_15, indent=0]
----
=== Health Check
Messaging in Helidon has built in health probes for liveness and readiness. To activate it
add the <>.
* Liveness - channel is considered UP until `cancel` or `onError` signal is intercepted on it.
* Readiness - channel is considered DOWN until `onSubscribe` signal is intercepted on it.
If you check your health endpoints `/health/live` and `/health/ready` you will discover
every messaging channel to have its own probe.
[source,json]
----
{
"name": "messaging",
"state": "UP",
"status": "UP",
"data": {
"my-channel-1": "UP",
"my-channel-2": "UP"
}
}
----
CAUTION: Due to the nack support are exceptions thrown in messaging methods NOT translated to error and cancel signals implicitly anymore
== Configuration
The channel must be configured to use connector as its upstream or downstream.
[source,yaml]
.Example of channel to connector mapping config:
----
mp.messaging.outgoing.to-connector-channel.connector: example-connector #<1>
mp.messaging.incoming.from-connector-channel.connector: example-connector #<2>
----
<1> Use connector `example-connector` as a downstream for channel `to-connector-channel` to consume the messages from the channel
<2> Use connector `example-connector` as an upstream for channel `to-connector-channel` to produce messages to the channel
[source,java]
.Example producing to connector:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_16, indent=0]
----
[source,java]
.Example consuming from connector:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_17, indent=0]
----
When the connector constructs a publisher or subscriber for a given channel,
it can access general connector configuration and channel-specific properties merged together with
special synthetic property `channel-name`.
image::msg/connector-config.svg[Connector config]
Connector specific config (1) merged together with global connector config (2).
[source,java]
.Example connector accessing configuration:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_18, indent=0]
----
<1> Config context is merged from channel and connector contexts
<2> Name of the channel requesting publisher as it's upstream from this connector
[source,yaml]
.Example of channel to connector mapping config with custom properties:
----
mp.messaging.incoming.from-connector-channel.connector: example-connector<1>
mp.messaging.incoming.from-connector-channel.channel-specific-prop: foo<2>
mp.messaging.connector.example-connector.connector-specific-prop: bar<3>
----
<1> Channel -> Connector mapping
<2> Channel configuration properties
<3> Connector configuration properties
[source,java]
.Example consuming from connector:
----
include::{sourcedir}/mp/reactivemessaging/IntroductionSnippets.java[tag=snippet_19, indent=0]
----
== Reference
* link:https://helidon.io/docs/v4/apidocs/io.helidon.microprofile.messaging/module-summary.html[Helidon MicroProfile Reactive Messaging]
* link:{microprofile-reactive-messaging-spec-url}[MicroProfile Reactive Messaging Specification]
* link:https://github.com/eclipse/microprofile-reactive-messaging[MicroProfile Reactive Messaging on GitHub]