mirror of
https://github.com/jlengrand/helidon.git
synced 2026-03-10 08:21:17 +00:00
SE Messaging doc (#2029)
* SE Messaging doc Signed-off-by: Daniel Kec <daniel.kec@oracle.com>
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
:spec-name: MicroProfile Reactive Messaging
|
:spec-name: MicroProfile Reactive Messaging
|
||||||
:description: {spec-name} support in Helidon MP
|
:description: {spec-name} support in Helidon MP
|
||||||
:keywords: helidon, mp, microprofile, messaging
|
:keywords: helidon, mp, microprofile, messaging
|
||||||
|
:h1Prefix: MP
|
||||||
|
|
||||||
== Reactive Messaging
|
== Reactive Messaging
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,11 @@
|
|||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
= Message
|
= Message
|
||||||
|
:toc:
|
||||||
|
:toc-placement: preamble
|
||||||
|
:description: Reactive Messaging Message in Helidon MP
|
||||||
|
:keywords: helidon, mp, messaging, message
|
||||||
|
:h1Prefix: MP
|
||||||
|
|
||||||
== Message
|
== Message
|
||||||
The Reactive Messaging
|
The Reactive Messaging
|
||||||
@@ -69,7 +74,7 @@ https://download.eclipse.org/microprofile/microprofile-reactive-messaging-1.0/mi
|
|||||||
@Outgoing("consume-and-ack")
|
@Outgoing("consume-and-ack")
|
||||||
public PublisherBuilder<Integer> streamOfMessages() {
|
public PublisherBuilder<Integer> streamOfMessages() {
|
||||||
return ReactiveStreams.of(Message.of("This is Payload", () -> {
|
return ReactiveStreams.of(Message.of("This is Payload", () -> {
|
||||||
System.out.println("This articular message was acked!");
|
System.out.println("This particular message was acked!");
|
||||||
return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
})).buildRs();
|
})).buildRs();
|
||||||
}
|
}
|
||||||
@@ -77,10 +82,10 @@ public PublisherBuilder<Integer> streamOfMessages() {
|
|||||||
@Incoming("consume-and-ack")
|
@Incoming("consume-and-ack")
|
||||||
@Acknowledgment(Acknowledgment.Strategy.MANUAL)
|
@Acknowledgment(Acknowledgment.Strategy.MANUAL)
|
||||||
public void receiveAndAckMessage(Message<String> msg) {
|
public void receiveAndAckMessage(Message<String> msg) {
|
||||||
// Calling ack() will print "This articular message was acked!" to System.out
|
msg.ack();<1>
|
||||||
msg.ack();
|
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
<1> Calling ack() will print "This particular message was acked!" to System.out
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
.Example of manual acknowledgment
|
.Example of manual acknowledgment
|
||||||
@@ -88,7 +93,7 @@ public void receiveAndAckMessage(Message<String> msg) {
|
|||||||
@Outgoing("consume-and-ack")
|
@Outgoing("consume-and-ack")
|
||||||
public PublisherBuilder<Integer> streamOfMessages() {
|
public PublisherBuilder<Integer> streamOfMessages() {
|
||||||
return ReactiveStreams.of(Message.of("This is Payload", () -> {
|
return ReactiveStreams.of(Message.of("This is Payload", () -> {
|
||||||
System.out.println("This articular message was acked!");
|
System.out.println("This particular message was acked!");
|
||||||
return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
})).buildRs();
|
})).buildRs();
|
||||||
}
|
}
|
||||||
@@ -96,24 +101,25 @@ public PublisherBuilder<Integer> streamOfMessages() {
|
|||||||
@Incoming("consume-and-ack")
|
@Incoming("consume-and-ack")
|
||||||
@Acknowledgment(Acknowledgment.Strategy.MANUAL)
|
@Acknowledgment(Acknowledgment.Strategy.MANUAL)
|
||||||
public void receiveAndAckMessage(Message<String> msg) {
|
public void receiveAndAckMessage(Message<String> msg) {
|
||||||
// Calling ack() will print "This articular message was acked!" to System.out
|
msg.ack();<1>
|
||||||
msg.ack();
|
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
<1> Calling ack() will print "This particular message was acked!" to System.out
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
.Example of explicit pre-process acknowledgment
|
.Example of explicit pre-process acknowledgment
|
||||||
----
|
----
|
||||||
@Outgoing("consume-and-ack")
|
@Outgoing("consume-and-ack")
|
||||||
public PublisherBuilder<Integer> streamOfMessages() {
|
public PublisherBuilder<Integer> streamOfMessages() {
|
||||||
return ReactiveStreams.of(Message.of("This is Payload", () -> {
|
return ReactiveStreams.of(Message.of("This is Payload", () -> {
|
||||||
System.out.println("This articular message was acked!");
|
System.out.println("This particular message was acked!");
|
||||||
return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
})).buildRs();
|
})).buildRs();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prints to the console:
|
* Prints to the console:
|
||||||
* > This articular message was acked!
|
* > This particular message was acked!
|
||||||
* > Method invocation!
|
* > Method invocation!
|
||||||
*/
|
*/
|
||||||
@Incoming("consume-and-ack")
|
@Incoming("consume-and-ack")
|
||||||
@@ -128,7 +134,7 @@ public void receiveAndAckMessage(Message<String> msg) {
|
|||||||
@Outgoing("consume-and-ack")
|
@Outgoing("consume-and-ack")
|
||||||
public PublisherBuilder<Integer> streamOfMessages() {
|
public PublisherBuilder<Integer> streamOfMessages() {
|
||||||
return ReactiveStreams.of(Message.of("This is Payload", () -> {
|
return ReactiveStreams.of(Message.of("This is Payload", () -> {
|
||||||
System.out.println("This articular message was acked!");
|
System.out.println("This particular message was acked!");
|
||||||
return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
})).buildRs();
|
})).buildRs();
|
||||||
}
|
}
|
||||||
@@ -136,7 +142,7 @@ public PublisherBuilder<Integer> streamOfMessages() {
|
|||||||
/**
|
/**
|
||||||
* Prints to the console:
|
* Prints to the console:
|
||||||
* > Method invocation!
|
* > Method invocation!
|
||||||
* > This articular message was acked!
|
* > This particular message was acked!
|
||||||
*/
|
*/
|
||||||
@Incoming("consume-and-ack")
|
@Incoming("consume-and-ack")
|
||||||
@Acknowledgment(Acknowledgment.Strategy.POST_PROCESSING)
|
@Acknowledgment(Acknowledgment.Strategy.POST_PROCESSING)
|
||||||
|
|||||||
@@ -17,8 +17,13 @@
|
|||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
= Connector
|
= Connector
|
||||||
|
:toc:
|
||||||
|
:toc-placement: preamble
|
||||||
|
:description: Reactive Messaging Connector in Helidon MP
|
||||||
|
:keywords: helidon, mp, messaging, connector
|
||||||
|
:h1Prefix: MP
|
||||||
|
|
||||||
== Connector Bean
|
== Messaging Connector Bean
|
||||||
|
|
||||||
Messaging connector is just an application scoped bean which implements
|
Messaging connector is just an application scoped bean which implements
|
||||||
`IncomingConnectorFactory`, `OutgoingConnectorFactory` or both.
|
`IncomingConnectorFactory`, `OutgoingConnectorFactory` or both.
|
||||||
@@ -89,8 +94,7 @@ public class ExampleConnector implements IncomingConnectorFactory {
|
|||||||
@Override
|
@Override
|
||||||
public PublisherBuilder<? extends Message<?>> getPublisherBuilder(final Config config) {
|
public PublisherBuilder<? extends Message<?>> getPublisherBuilder(final Config config) {
|
||||||
|
|
||||||
// Config context is merged from channel and connector contexts
|
String firstPropValue = config.getValue("first-test-prop", String.class);<1>
|
||||||
String firstPropValue = config.getValue("first-test-prop", String.class);
|
|
||||||
String secondPropValue = config.getValue("second-test-prop", String.class);
|
String secondPropValue = config.getValue("second-test-prop", String.class);
|
||||||
|
|
||||||
return ReactiveStreams.of(firstPropValue, secondPropValue)
|
return ReactiveStreams.of(firstPropValue, secondPropValue)
|
||||||
@@ -98,17 +102,18 @@ public class ExampleConnector implements IncomingConnectorFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
<1> Config context is merged from channel and connector contexts
|
||||||
|
|
||||||
[source,yaml]
|
[source,yaml]
|
||||||
.Example of channel to connector mapping config with custom properties:
|
.Example of channel to connector mapping config with custom properties:
|
||||||
----
|
----
|
||||||
# Channel -> Connector mapping
|
mp.messaging.incoming.from-connector-channel.connector: example-connector<1>
|
||||||
mp.messaging.incoming.from-connector-channel.connector: example-connector
|
mp.messaging.incoming.from-connector-channel.first-test-prop: foo<2>
|
||||||
# Channel configuration properties
|
mp.messaging.connector.example-connector.second-test-prop: bar<3>
|
||||||
mp.messaging.incoming.from-connector-channel.first-test-prop: foo
|
|
||||||
# Connector configuration properties
|
|
||||||
mp.messaging.connector.example-connector.second-test-prop: bar
|
|
||||||
----
|
----
|
||||||
|
<1> Channel -> Connector mapping
|
||||||
|
<2> Channel configuration properties
|
||||||
|
<3> Connector configuration properties
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
.Example consuming from connector:
|
.Example consuming from connector:
|
||||||
|
|||||||
88
docs/mp/reactivemessaging/04_kafka.adoc
Normal file
88
docs/mp/reactivemessaging/04_kafka.adoc
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
Copyright (c) 2020 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.
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
= Kafka Connector
|
||||||
|
:toc:
|
||||||
|
:toc-placement: preamble
|
||||||
|
:description: Reactive Messaging support for Kafka in Helidon MP
|
||||||
|
:keywords: helidon, mp, messaging, kafka
|
||||||
|
:h1Prefix: MP
|
||||||
|
|
||||||
|
== Reactive Kafka Connector
|
||||||
|
Connecting streams to Kafka with Reactive Messaging couldn't be easier.
|
||||||
|
|
||||||
|
[source,xml]
|
||||||
|
.Dependencies needed:
|
||||||
|
----
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.helidon.microprofile.messaging</groupId>
|
||||||
|
<artifactId>helidon-microprofile-messaging</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.helidon.messaging.kafka</groupId>
|
||||||
|
<artifactId>helidon-messaging-kafka</artifactId>
|
||||||
|
</dependency>
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,yaml]
|
||||||
|
.Example of connector config:
|
||||||
|
----
|
||||||
|
mp.messaging:
|
||||||
|
|
||||||
|
incoming.from-kafka:
|
||||||
|
connector: helidon-kafka
|
||||||
|
topic: messaging-test-topic-1
|
||||||
|
auto.offset.reset: latest
|
||||||
|
enable.auto.commit: true
|
||||||
|
group.id: example-group-id
|
||||||
|
|
||||||
|
outgoing.to-kafka:
|
||||||
|
connector: helidon-kafka
|
||||||
|
topic: messaging-test-topic-1
|
||||||
|
|
||||||
|
connector:
|
||||||
|
helidon-kafka:
|
||||||
|
bootstrap.servers: localhost:9092
|
||||||
|
key.serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||||
|
value.serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||||
|
key.deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||||
|
value.deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example of consuming from Kafka:
|
||||||
|
----
|
||||||
|
@Incoming("from-kafka")
|
||||||
|
public void consumeKafka(String msg) {
|
||||||
|
System.out.println("Kafka says: " + msg);
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example of producing to Kafka:
|
||||||
|
----
|
||||||
|
@Outgoing("to-kafka")
|
||||||
|
public PublisherBuilder<String> produceToKafka() {
|
||||||
|
return ReactiveStreams.of("test1", "test2");
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
Don't forget to check out the examples with pre-configured Kafka docker image, for easy testing:
|
||||||
|
|
||||||
|
* https://github.com/oracle/helidon/tree/master/examples/messaging
|
||||||
@@ -20,6 +20,113 @@
|
|||||||
:toc:
|
:toc:
|
||||||
:toc-placement: preamble
|
:toc-placement: preamble
|
||||||
:description: Reactive Messaging support in Helidon SE
|
:description: Reactive Messaging support in Helidon SE
|
||||||
:keywords: helidon, mp, messaging
|
:keywords: helidon, se, messaging
|
||||||
|
:h1Prefix: SE
|
||||||
|
|
||||||
== This page is Under Construction and will be available soon
|
== Reactive Messaging
|
||||||
|
|
||||||
|
Asynchronous messaging is a commonly used form of communication in the world of microservices.
|
||||||
|
While its possible to start building your reactive streams directly by combining operators and
|
||||||
|
connecting them to reactive APIs, with Helidon SE Reactive Messaging, you can now use prepared
|
||||||
|
tools for repetitive use case scenarios .
|
||||||
|
|
||||||
|
For example connecting your streams to external services usually requires a lot of boiler-plate
|
||||||
|
code for configuration handling, backpressure propagation, acknowledgement and more.
|
||||||
|
|
||||||
|
For such tasks there is a system of connectors, emitters and means to orchestrate them in Helidon,
|
||||||
|
called *Reactive Messaging*. It's basically an API for connecting and configuring
|
||||||
|
Connectors and Emitters with your reactive streams thru so called <<Channel,Channels>>.
|
||||||
|
|
||||||
|
You may wonder how *Reactive Messaging* relates to
|
||||||
|
<<mp/reactivemessaging/01_introduction.adoc,MicroProfile Reactive Messaging>>.
|
||||||
|
As the making of connectors or even configuring them can be repetitive task leading to
|
||||||
|
the same results, Helidon SE Reactive Messaging supports very same configuration format
|
||||||
|
for connectors as its MicroProfile counterpart does. Also, MP Connectors are reusable in
|
||||||
|
Helidon SE Messaging with some limitation(there is no CDI in Helidon SE).
|
||||||
|
All Messaging connectors in Helidon are made to be universally usable by Helidon MP and SE.
|
||||||
|
|
||||||
|
=== Channel
|
||||||
|
Channel is a named pair of `Publisher` and `Subscriber`. Channels can be connected together by
|
||||||
|
<<Processor,processors>>. Registering of `Publisher` or `Subscriber` for a channel can be done
|
||||||
|
by Messaging API, or configured implicitly for using registered <<se/reactivemessaging/03_connector.adoc,connector>>
|
||||||
|
for generating such `Publisher` or `Subscriber`.
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example of simple channel:
|
||||||
|
----
|
||||||
|
Channel<String> channel1 = Channel.create("channel1");
|
||||||
|
|
||||||
|
Messaging.builder()
|
||||||
|
.publisher(channel1, Multi.just("message 1", "message 2")
|
||||||
|
.map(Message::of))
|
||||||
|
.listener(channel1, s -> System.out.println("Intecepted message " + s))
|
||||||
|
.build()
|
||||||
|
.start();
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Processor
|
||||||
|
Processor is a typical reactive processor acting as a `Subscriber` to upstream and as a `Publisher`
|
||||||
|
to downstream. In terms of reactive messaging it is able to connect two <<Channel,channels>> to one
|
||||||
|
reactive stream.
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example of processor usage:
|
||||||
|
----
|
||||||
|
Channel<String> firstChannel = Channel.create("first-channel");
|
||||||
|
Channel<String> secondChannel = Channel.create("second-channel");
|
||||||
|
|
||||||
|
Messaging.builder()
|
||||||
|
.publisher(secondChannel, ReactiveStreams.of("test1", "test2", "test3")
|
||||||
|
.map(Message::of))
|
||||||
|
.processor(secondChannel, firstChannel, ReactiveStreams.<Message<String>>builder()
|
||||||
|
.map(Message::getPayload)
|
||||||
|
.map(String::toUpperCase)
|
||||||
|
.map(Message::of)
|
||||||
|
)
|
||||||
|
.subscriber(firstChannel, ReactiveStreams.<Message<String>>builder()
|
||||||
|
.peek(Message::ack)
|
||||||
|
.map(Message::getPayload)
|
||||||
|
.forEach(s -> System.out.println("Consuming message " + s)))
|
||||||
|
.build()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
>Consuming message TEST1
|
||||||
|
>Consuming message TEST2
|
||||||
|
>Consuming message TEST3
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Message
|
||||||
|
Reactive Messaging in Helidon SE uses the same concept of
|
||||||
|
<<mp/reactivemessaging/02_message.adoc,message wrapping>> as MicroProfile messaging.
|
||||||
|
The only notable difference is that SE Messaging does almost no implicit or automatic
|
||||||
|
acknowledgement due to _no magic_ philosophy of Helidon SE.
|
||||||
|
|
||||||
|
Only exception to this are variants of methods `Messaging.Builder#listener` and
|
||||||
|
`Messaging.Builder#processor` with consumer or function params, conveniently unwrapping payload
|
||||||
|
for you. After such implicit unwrapping is not possible to do a manual acknowledgement, therefore
|
||||||
|
implicit ack before callback is executed is necessary.
|
||||||
|
|
||||||
|
=== Connector
|
||||||
|
Connector concept is a way for connecting <<Channel,channels>> to external sources.
|
||||||
|
To make <<se/reactivemessaging/03_connector.adoc,creation and usage of connectors>>
|
||||||
|
as easy and versatile as possible, Helidon SE Messaging uses same API for connectors
|
||||||
|
like <<mp/reactivemessaging/01_introduction.adoc,MicroProfile Reactive Messaging>> does.
|
||||||
|
This allows connectors to be usable in both flavors of Helidon with one limitation which is
|
||||||
|
that connector has to be able to work without CDI.
|
||||||
|
|
||||||
|
Example of such a versatile connector is Helidon's own:
|
||||||
|
|
||||||
|
* <<se/reactivemessaging/04_kafka.adoc,Kafka connector>>
|
||||||
|
|
||||||
|
|
||||||
|
=== Dependency
|
||||||
|
|
||||||
|
Declare the following dependency in your project:
|
||||||
|
|
||||||
|
[source,xml]
|
||||||
|
----
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.helidon.messaging</groupId>
|
||||||
|
<artifactId>helidon-messaging</artifactId>
|
||||||
|
</dependency>
|
||||||
|
----
|
||||||
198
docs/se/reactivemessaging/03_connector.adoc
Normal file
198
docs/se/reactivemessaging/03_connector.adoc
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
Copyright (c) 2020 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.
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
= Connector
|
||||||
|
:toc:
|
||||||
|
:toc-placement: preamble
|
||||||
|
:description: Reactive Messaging Connector in Helidon SE
|
||||||
|
:keywords: helidon, se, messaging, connector
|
||||||
|
:h1Prefix: SE
|
||||||
|
|
||||||
|
== Messaging Connector
|
||||||
|
Connector for Reactive Messaging is a factory producing Publishers and Subscribers for
|
||||||
|
Channels in Reactive Messaging. Messaging connector is just an implementation of
|
||||||
|
`IncomingConnectorFactory`, `OutgoingConnectorFactory` or both.
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example connector `example-connector`:
|
||||||
|
----
|
||||||
|
@Connector("example-connector")
|
||||||
|
public class ExampleConnector implements IncomingConnectorFactory, OutgoingConnectorFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PublisherBuilder<? extends Message<?>> getPublisherBuilder(Config config) {
|
||||||
|
return ReactiveStreams.of("foo", "bar")
|
||||||
|
.map(Message::of);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubscriberBuilder<? extends Message<?>, Void> getSubscriberBuilder(Config config) {
|
||||||
|
return ReactiveStreams.<Message<?>>builder()
|
||||||
|
.map(Message::getPayload)
|
||||||
|
.forEach(o -> System.out.println("Connector says: " + o));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,yaml]
|
||||||
|
.Example of channel to connector mapping config:
|
||||||
|
----
|
||||||
|
mp.messaging.outgoing.to-connector-channel.connector: example-connector
|
||||||
|
mp.messaging.incoming.from-connector-channel.connector: example-connector
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example producing to connector:
|
||||||
|
----
|
||||||
|
Messaging.builder()
|
||||||
|
.connector(new ExampleConnector())
|
||||||
|
.publisher(Channel.create("to-connector-channel"),
|
||||||
|
ReactiveStreams.of("fee", "fie")
|
||||||
|
.map(Message::of)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
> Connector says: fee
|
||||||
|
> Connector says: fie
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example consuming from connector:
|
||||||
|
----
|
||||||
|
Messaging.builder()
|
||||||
|
.connector(new ExampleConnector())
|
||||||
|
.subscriber(Channel.create("from-connector-channel"),
|
||||||
|
ReactiveStreams.<Message<String>>builder()
|
||||||
|
.peek(Message::ack)
|
||||||
|
.map(Message::getPayload)
|
||||||
|
.forEach(s -> System.out.println("Consuming: " + s))
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
> Consuming: foo
|
||||||
|
> Consuming: bar
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Configuration
|
||||||
|
Messaging connector in Helidon SE can be configured explicitly by API or implicitly
|
||||||
|
by config following notation of link:https://download.eclipse.org/microprofile/microprofile-reactive-messaging-1.0/microprofile-reactive-messaging-spec.html#_configuration[MicroProfile Reactive Messaging].
|
||||||
|
|
||||||
|
Configuration is being supplied to connector by Messaging implementation,
|
||||||
|
two mandatory attributes are always present:
|
||||||
|
|
||||||
|
* `channel-name` name of the channel which has this connector configured as Publisher or Subscriber, `Channel.create('name-of-channel')` in case of explicit configuration or `mp.messaging.incoming.name-of-channel.connector: connector-name` in case of implicit config
|
||||||
|
* `connector` name of the connector `@Connector("connector-name")`
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example connector accessing configuration:
|
||||||
|
----
|
||||||
|
@Connector("example-connector")
|
||||||
|
public class ExampleConnector implements IncomingConnectorFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PublisherBuilder<? extends Message<?>> getPublisherBuilder(final Config config) {
|
||||||
|
|
||||||
|
String firstPropValue = config.getValue("first-test-prop", String.class);<1>
|
||||||
|
String secondPropValue = config.getValue("second-test-prop", String.class);
|
||||||
|
|
||||||
|
return ReactiveStreams.of(firstPropValue, secondPropValue)
|
||||||
|
.map(Message::of);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
<1> Config context is merged from channel and connector contexts
|
||||||
|
|
||||||
|
==== Explicit Config
|
||||||
|
|
||||||
|
An explicit config for channel's publisher is possible with `Channel.Builder#publisherConfig(Config config)`
|
||||||
|
and for subscriber with `Channel.Builder#subscriberConfig(Config config)`.
|
||||||
|
Supplied <<se/config/01_introduction.adoc,Heldion Config>> is merged with
|
||||||
|
mandatory attributes and any implicit config found. Resulting config is served to Connector.
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example consuming from Kafka connector with explicit config:
|
||||||
|
----
|
||||||
|
String kafkaServer = config.get("app.kafka.bootstrap.servers").asString().get();
|
||||||
|
String topic = config.get("app.kafka.topic").asString().get();
|
||||||
|
|
||||||
|
Channel<String> fromKafka = Channel.<String>builder()<1><2>
|
||||||
|
.name("from-kafka")
|
||||||
|
.publisherConfig(KafkaConnector.configBuilder()
|
||||||
|
.bootstrapServers(kafkaServer)
|
||||||
|
.groupId("example-group-" + session.getId())
|
||||||
|
.topic(topic)
|
||||||
|
.autoOffsetReset(KafkaConfigBuilder.AutoOffsetReset.LATEST)
|
||||||
|
.enableAutoCommit(true)
|
||||||
|
.keyDeserializer(StringDeserializer.class)
|
||||||
|
.valueDeserializer(StringDeserializer.class)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
KafkaConnector kafkaConnector = KafkaConnector.create();<3>
|
||||||
|
|
||||||
|
Messaging messaging = Messaging.builder()
|
||||||
|
.connector(kafkaConnector)
|
||||||
|
.listener(fromKafka, payload -> {
|
||||||
|
System.out.println("Kafka says: " + payload);
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
.start();
|
||||||
|
----
|
||||||
|
<1> Prepare channel for connecting kafka connector with specific publisher configuration -> listener,
|
||||||
|
<2> Channel -> connector mapping is automatic when using `KafkaConnector.configBuilder()`
|
||||||
|
<3> Prepare Kafka connector, can be used by any channel
|
||||||
|
|
||||||
|
==== Implicit Config
|
||||||
|
Implicit config without any hard-coding is possible with <<se/config/01_introduction.adoc,Heldion Config>> following notation of link:https://download.eclipse.org/microprofile/microprofile-reactive-messaging-1.0/microprofile-reactive-messaging-spec.html#_configuration[MicroProfile Reactive Messaging].
|
||||||
|
|
||||||
|
[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.first-test-prop: foo<2>
|
||||||
|
mp.messaging.connector.example-connector.second-test-prop: bar<3>
|
||||||
|
----
|
||||||
|
<1> Channel -> Connector mapping
|
||||||
|
<2> Channel configuration properties
|
||||||
|
<3> Connector configuration properties
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example consuming from connector:
|
||||||
|
----
|
||||||
|
Messaging.builder()
|
||||||
|
.connector(new ExampleConnector())
|
||||||
|
.listener(Channel.create("from-connector-channel"),
|
||||||
|
s -> System.out.println("Consuming: " + s))
|
||||||
|
.build()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
> Consuming: foo
|
||||||
|
> Consuming: bar
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Reusability in MP Messaging
|
||||||
|
As the API is the same for <<mp/reactivemessaging/01_introduction.adoc,MicroProfile Reactive Messaging>>
|
||||||
|
connectors, all that is needed to make connector work in both ways is annotating it with
|
||||||
|
`@ApplicationScoped`. Such connector is treated as a bean in Helidon MP.
|
||||||
|
|
||||||
|
For specific informations about creating messaging connectors for Helidon MP visit
|
||||||
|
<<mp/reactivemessaging/03_connector.adoc,Messaging Connector Bean>>.
|
||||||
166
docs/se/reactivemessaging/04_kafka.adoc
Normal file
166
docs/se/reactivemessaging/04_kafka.adoc
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
Copyright (c) 2020 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.
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
= Kafka Connector
|
||||||
|
:toc:
|
||||||
|
:toc-placement: preamble
|
||||||
|
:description: Reactive Messaging support for Kafka in Helidon SE
|
||||||
|
:keywords: helidon, se, messaging, kafka
|
||||||
|
:h1Prefix: SE
|
||||||
|
|
||||||
|
== Reactive Kafka Connector
|
||||||
|
Connecting streams to Kafka with Reactive Messaging couldn't be easier.
|
||||||
|
|
||||||
|
[source,xml]
|
||||||
|
.Dependencies needed:
|
||||||
|
----
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.helidon.messaging</groupId>
|
||||||
|
<artifactId>helidon-messaging</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.helidon.messaging.kafka</groupId>
|
||||||
|
<artifactId>helidon-messaging-kafka</artifactId>
|
||||||
|
</dependency>
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Explicit config with config builder
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example of consuming from Kafka:
|
||||||
|
----
|
||||||
|
String kafkaServer = config.get("app.kafka.bootstrap.servers").asString().get();
|
||||||
|
String topic = config.get("app.kafka.topic").asString().get();
|
||||||
|
|
||||||
|
Channel<String> fromKafka = Channel.<String>builder()<1><2>
|
||||||
|
.name("from-kafka")
|
||||||
|
.publisherConfig(KafkaConnector.configBuilder()
|
||||||
|
.bootstrapServers(kafkaServer)
|
||||||
|
.groupId("example-group-" + session.getId())
|
||||||
|
.topic(topic)
|
||||||
|
.autoOffsetReset(KafkaConfigBuilder.AutoOffsetReset.LATEST)
|
||||||
|
.enableAutoCommit(true)
|
||||||
|
.keyDeserializer(StringDeserializer.class)
|
||||||
|
.valueDeserializer(StringDeserializer.class)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
KafkaConnector kafkaConnector = KafkaConnector.create();<3>
|
||||||
|
|
||||||
|
Messaging messaging = Messaging.builder()
|
||||||
|
.connector(kafkaConnector)
|
||||||
|
.listener(fromKafka, payload -> {
|
||||||
|
System.out.println("Kafka says: " + payload);
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
.start();
|
||||||
|
----
|
||||||
|
<1> Prepare a channel for connecting kafka connector with specific publisher configuration -> listener
|
||||||
|
<2> Channel -> connector mapping is automatic when using KafkaConnector.configBuilder()
|
||||||
|
<3> Prepare Kafka connector, can be used by any channel
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example of producing to Kafka:
|
||||||
|
----
|
||||||
|
String kafkaServer = config.get("app.kafka.bootstrap.servers").asString().get();
|
||||||
|
String topic = config.get("app.kafka.topic").asString().get();
|
||||||
|
|
||||||
|
Channel<String> toKafka = Channel.<String>builder()<1><2>
|
||||||
|
.subscriberConfig(KafkaConnector.configBuilder()
|
||||||
|
.bootstrapServers(kafkaServer)
|
||||||
|
.topic(topic)
|
||||||
|
.keySerializer(StringSerializer.class)
|
||||||
|
.valueSerializer(StringSerializer.class)
|
||||||
|
.build()
|
||||||
|
).build();
|
||||||
|
|
||||||
|
KafkaConnector kafkaConnector = KafkaConnector.create();<3>
|
||||||
|
|
||||||
|
messaging = Messaging.builder()
|
||||||
|
.publisher(toKafka, Multi.just("test1", "test2").map(Message::of))
|
||||||
|
.connector(kafkaConnector)
|
||||||
|
.build()
|
||||||
|
.start();
|
||||||
|
----
|
||||||
|
<1> Prepare a channel for connecting kafka connector with specific publisher configuration -> listener
|
||||||
|
<2> Channel -> connector mapping is automatic when using KafkaConnector.configBuilder()
|
||||||
|
<3> Prepare Kafka connector, can be used by any channel
|
||||||
|
|
||||||
|
=== Implicit Helidon Config
|
||||||
|
|
||||||
|
[source,yaml]
|
||||||
|
.Example of connector config:
|
||||||
|
----
|
||||||
|
mp.messaging:
|
||||||
|
|
||||||
|
incoming.from-kafka:
|
||||||
|
connector: helidon-kafka
|
||||||
|
topic: messaging-test-topic-1
|
||||||
|
auto.offset.reset: latest
|
||||||
|
enable.auto.commit: true
|
||||||
|
group.id: example-group-id
|
||||||
|
|
||||||
|
outgoing.to-kafka:
|
||||||
|
connector: helidon-kafka
|
||||||
|
topic: messaging-test-topic-1
|
||||||
|
|
||||||
|
connector:
|
||||||
|
helidon-kafka:
|
||||||
|
bootstrap.servers: localhost:9092
|
||||||
|
key.serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||||
|
value.serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||||
|
key.deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||||
|
value.deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||||
|
----
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example of consuming from Kafka:
|
||||||
|
----
|
||||||
|
Channel<String> fromKafka = Channel.create("from-kafka");
|
||||||
|
|
||||||
|
KafkaConnector kafkaConnector = KafkaConnector.create();<1>
|
||||||
|
|
||||||
|
Messaging messaging = Messaging.builder()
|
||||||
|
.connector(kafkaConnector)
|
||||||
|
.listener(fromKafka, payload -> {
|
||||||
|
System.out.println("Kafka says: " + payload);
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
.start();
|
||||||
|
----
|
||||||
|
<1> Prepare Kafka connector, can be used by any channel
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
.Example of producing to Kafka:
|
||||||
|
----
|
||||||
|
Channel<String> toKafka = Channel.create("to-kafka");
|
||||||
|
|
||||||
|
KafkaConnector kafkaConnector = KafkaConnector.create();<1>
|
||||||
|
|
||||||
|
messaging = Messaging.builder()
|
||||||
|
.publisher(toKafka, Multi.just("test1", "test2").map(Message::of))
|
||||||
|
.connector(kafkaConnector)
|
||||||
|
.build()
|
||||||
|
.start();
|
||||||
|
----
|
||||||
|
<1> Prepare Kafka connector, can be used by any channel
|
||||||
|
|
||||||
|
Don't forget to check out the examples with pre-configured Kafka docker image, for easy testing:
|
||||||
|
|
||||||
|
* https://github.com/oracle/helidon/tree/master/examples/messaging
|
||||||
@@ -22,9 +22,6 @@ Helidon has its own set of reactive operators that have no dependencies outside
|
|||||||
These operators can be used with `java.util.concurrent.Flow` based reactive streams.
|
These operators can be used with `java.util.concurrent.Flow` based reactive streams.
|
||||||
Stream processing operator chain can be easily constructed by `io.helidon.common.reactive.Multi`, or
|
Stream processing operator chain can be easily constructed by `io.helidon.common.reactive.Multi`, or
|
||||||
`io.helidon.common.reactive.Single` for streams with single value.
|
`io.helidon.common.reactive.Single` for streams with single value.
|
||||||
Implementation was contributed to Helidon by the world-renown reactive programming expert,
|
|
||||||
project lead of RxJava and co-father of project Reactor,
|
|
||||||
https://twitter.com/akarnokd[Dr. David Karnok].
|
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
.Example of Multi usage:
|
.Example of Multi usage:
|
||||||
|
|||||||
Reference in New Issue
Block a user