* merge content of stream-integrations.md Ref #23052 * use symlink in java counterpart * make file a symlink
This commit is contained in:
parent
dd930f4835
commit
e93ae613d1
2 changed files with 166 additions and 563 deletions
|
|
@ -1,514 +0,0 @@
|
||||||
# Integration
|
|
||||||
|
|
||||||
## Integrating with Actors
|
|
||||||
|
|
||||||
For piping the elements of a stream as messages to an ordinary actor you can use
|
|
||||||
`ask` in a `mapAsync` or use `Sink.actorRefWithAck`.
|
|
||||||
|
|
||||||
Messages can be sent to a stream with `Source.queue` or via the `ActorRef` that is
|
|
||||||
materialized by `Source.actorRef`.
|
|
||||||
|
|
||||||
### mapAsync + ask
|
|
||||||
|
|
||||||
A nice way to delegate some processing of elements in a stream to an actor is to
|
|
||||||
use `ask` in `mapAsync`. The back-pressure of the stream is maintained by
|
|
||||||
the `CompletionStage` of the `ask` and the mailbox of the actor will not be filled with
|
|
||||||
more messages than the given `parallelism` of the `mapAsync` stage.
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #mapAsync-ask }
|
|
||||||
|
|
||||||
Note that the messages received in the actor will be in the same order as
|
|
||||||
the stream elements, i.e. the `parallelism` does not change the ordering
|
|
||||||
of the messages. There is a performance advantage of using parallelism > 1
|
|
||||||
even though the actor will only process one message at a time because then there
|
|
||||||
is already a message in the mailbox when the actor has completed previous
|
|
||||||
message.
|
|
||||||
|
|
||||||
The actor must reply to the `getSender()` for each message from the stream. That
|
|
||||||
reply will complete the `CompletionStage` of the `ask` and it will be the element that
|
|
||||||
is emitted downstreams from `mapAsync`.
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #ask-actor }
|
|
||||||
|
|
||||||
The stream can be completed with failure by sending `akka.actor.Status.Failure`
|
|
||||||
as reply from the actor.
|
|
||||||
|
|
||||||
If the `ask` fails due to timeout the stream will be completed with
|
|
||||||
`TimeoutException` failure. If that is not desired outcome you can use `recover`
|
|
||||||
on the `ask` `CompletionStage`.
|
|
||||||
|
|
||||||
If you don't care about the reply values and only use them as back-pressure signals you
|
|
||||||
can use `Sink.ignore` after the `mapAsync` stage and then actor is effectively a sink
|
|
||||||
of the stream.
|
|
||||||
|
|
||||||
The same pattern can be used with @ref:[Actor routers](../routing.md). Then you
|
|
||||||
can use `mapAsyncUnordered` for better efficiency if you don't care about the
|
|
||||||
order of the emitted downstream elements (the replies).
|
|
||||||
|
|
||||||
### Sink.actorRefWithAck
|
|
||||||
|
|
||||||
The sink sends the elements of the stream to the given `ActorRef` that sends back back-pressure signal.
|
|
||||||
First element is always *onInitMessage*, then stream is waiting for the given acknowledgement message
|
|
||||||
from the given actor which means that it is ready to process elements. It also requires the given acknowledgement
|
|
||||||
message after each stream element to make back-pressure work.
|
|
||||||
|
|
||||||
If the target actor terminates the stream will be cancelled. When the stream is completed successfully the
|
|
||||||
given `onCompleteMessage` will be sent to the destination actor. When the stream is completed with
|
|
||||||
failure a `akka.actor.Status.Failure` message will be sent to the destination actor.
|
|
||||||
|
|
||||||
@@@ note
|
|
||||||
|
|
||||||
Using `Sink.actorRef` or ordinary `tell` from a `map` or `foreach` stage means that there is
|
|
||||||
no back-pressure signal from the destination actor, i.e. if the actor is not consuming the messages
|
|
||||||
fast enough the mailbox of the actor will grow, unless you use a bounded mailbox with zero
|
|
||||||
*mailbox-push-timeout-time* or use a rate limiting stage in front. It's often better to
|
|
||||||
use `Sink.actorRefWithAck` or `ask` in `mapAsync`, though.
|
|
||||||
|
|
||||||
@@@
|
|
||||||
|
|
||||||
### Source.queue
|
|
||||||
|
|
||||||
`Source.queue` can be used for emitting elements to a stream from an actor (or from anything running outside
|
|
||||||
the stream). The elements will be buffered until the stream can process them. You can `offer` elements to
|
|
||||||
the queue and they will be emitted to the stream if there is demand from downstream, otherwise they will
|
|
||||||
be buffered until request for demand is received.
|
|
||||||
|
|
||||||
Use overflow strategy `akka.stream.OverflowStrategy.backpressure` to avoid dropping of elements if the
|
|
||||||
buffer is full.
|
|
||||||
|
|
||||||
`SourceQueue.offer` returns `CompletionStage<QueueOfferResult>` which completes with
|
|
||||||
`QueueOfferResult.enqueued` if element was added to buffer or sent downstream. It completes with
|
|
||||||
`QueueOfferResult.dropped` if element was dropped. Can also complete with `QueueOfferResult.Failure` -
|
|
||||||
when stream failed or `QueueOfferResult.QueueClosed` when downstream is completed.
|
|
||||||
|
|
||||||
When used from an actor you typically `pipe` the result of the `CompletionStage` back to the actor to
|
|
||||||
continue processing.
|
|
||||||
|
|
||||||
### Source.actorRef
|
|
||||||
|
|
||||||
Messages sent to the actor that is materialized by `Source.actorRef` will be emitted to the
|
|
||||||
stream if there is demand from downstream, otherwise they will be buffered until request for
|
|
||||||
demand is received.
|
|
||||||
|
|
||||||
Depending on the defined `OverflowStrategy` it might drop elements if there is no space
|
|
||||||
available in the buffer. The strategy `OverflowStrategy.backpressure` is not supported
|
|
||||||
for this Source type, i.e. elements will be dropped if the buffer is filled by sending
|
|
||||||
at a rate that is faster than the stream can consume. You should consider using `Source.queue`
|
|
||||||
if you want a backpressured actor interface.
|
|
||||||
|
|
||||||
The stream can be completed successfully by sending `akka.actor.PoisonPill` or
|
|
||||||
`akka.actor.Status.Success` to the actor reference.
|
|
||||||
|
|
||||||
The stream can be completed with failure by sending `akka.actor.Status.Failure` to the
|
|
||||||
actor reference.
|
|
||||||
|
|
||||||
The actor will be stopped when the stream is completed, failed or cancelled from downstream,
|
|
||||||
i.e. you can watch it to get notified when that happens.
|
|
||||||
|
|
||||||
## Integrating with External Services
|
|
||||||
|
|
||||||
Stream transformations and side effects involving external non-stream based services can be
|
|
||||||
performed with `mapAsync` or `mapAsyncUnordered`.
|
|
||||||
|
|
||||||
For example, sending emails to the authors of selected tweets using an external
|
|
||||||
email service:
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #email-server-send }
|
|
||||||
|
|
||||||
We start with the tweet stream of authors:
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #tweet-authors }
|
|
||||||
|
|
||||||
Assume that we can lookup their email address using:
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #email-address-lookup }
|
|
||||||
|
|
||||||
Transforming the stream of authors to a stream of email addresses by using the `lookupEmail`
|
|
||||||
service can be done with `mapAsync`:
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #email-addresses-mapAsync }
|
|
||||||
|
|
||||||
Finally, sending the emails:
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #send-emails }
|
|
||||||
|
|
||||||
`mapAsync` is applying the given function that is calling out to the external service to
|
|
||||||
each of the elements as they pass through this processing step. The function returns a `CompletionStage`
|
|
||||||
and the value of that future will be emitted downstreams. The number of Futures
|
|
||||||
that shall run in parallel is given as the first argument to `mapAsync`.
|
|
||||||
These Futures may complete in any order, but the elements that are emitted
|
|
||||||
downstream are in the same order as received from upstream.
|
|
||||||
|
|
||||||
That means that back-pressure works as expected. For example if the `emailServer.send`
|
|
||||||
is the bottleneck it will limit the rate at which incoming tweets are retrieved and
|
|
||||||
email addresses looked up.
|
|
||||||
|
|
||||||
The final piece of this pipeline is to generate the demand that pulls the tweet
|
|
||||||
authors information through the emailing pipeline: we attach a `Sink.ignore`
|
|
||||||
which makes it all run. If our email process would return some interesting data
|
|
||||||
for further transformation then we would of course not ignore it but send that
|
|
||||||
result stream onwards for further processing or storage.
|
|
||||||
|
|
||||||
Note that `mapAsync` preserves the order of the stream elements. In this example the order
|
|
||||||
is not important and then we can use the more efficient `mapAsyncUnordered`:
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #external-service-mapAsyncUnordered }
|
|
||||||
|
|
||||||
In the above example the services conveniently returned a `CompletionStage` of the result.
|
|
||||||
If that is not the case you need to wrap the call in a `CompletionStage`. If the service call
|
|
||||||
involves blocking you must also make sure that you run it on a dedicated execution context, to
|
|
||||||
avoid starvation and disturbance of other tasks in the system.
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #blocking-mapAsync }
|
|
||||||
|
|
||||||
The configuration of the `"blocking-dispatcher"` may look something like:
|
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #blocking-dispatcher-config }
|
|
||||||
|
|
||||||
An alternative for blocking calls is to perform them in a `map` operation, still using a
|
|
||||||
dedicated dispatcher for that operation.
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #blocking-map }
|
|
||||||
|
|
||||||
However, that is not exactly the same as `mapAsync`, since the `mapAsync` may run
|
|
||||||
several calls concurrently, but `map` performs them one at a time.
|
|
||||||
|
|
||||||
For a service that is exposed as an actor, or if an actor is used as a gateway in front of an
|
|
||||||
external service, you can use `ask`:
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #save-tweets }
|
|
||||||
|
|
||||||
Note that if the `ask` is not completed within the given timeout the stream is completed with failure.
|
|
||||||
If that is not desired outcome you can use `recover` on the `ask` `CompletionStage`.
|
|
||||||
|
|
||||||
### Illustrating ordering and parallelism
|
|
||||||
|
|
||||||
Let us look at another example to get a better understanding of the ordering
|
|
||||||
and parallelism characteristics of `mapAsync` and `mapAsyncUnordered`.
|
|
||||||
|
|
||||||
Several `mapAsync` and `mapAsyncUnordered` futures may run concurrently.
|
|
||||||
The number of concurrent futures are limited by the downstream demand.
|
|
||||||
For example, if 5 elements have been requested by downstream there will be at most 5
|
|
||||||
futures in progress.
|
|
||||||
|
|
||||||
`mapAsync` emits the future results in the same order as the input elements
|
|
||||||
were received. That means that completed results are only emitted downstream
|
|
||||||
when earlier results have been completed and emitted. One slow call will thereby
|
|
||||||
delay the results of all successive calls, even though they are completed before
|
|
||||||
the slow call.
|
|
||||||
|
|
||||||
`mapAsyncUnordered` emits the future results as soon as they are completed, i.e.
|
|
||||||
it is possible that the elements are not emitted downstream in the same order as
|
|
||||||
received from upstream. One slow call will thereby not delay the results of faster
|
|
||||||
successive calls as long as there is downstream demand of several elements.
|
|
||||||
|
|
||||||
Here is a fictive service that we can use to illustrate these aspects.
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #sometimes-slow-service }
|
|
||||||
|
|
||||||
Elements starting with a lower case character are simulated to take longer time
|
|
||||||
to process.
|
|
||||||
|
|
||||||
Here is how we can use it with `mapAsync`:
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #sometimes-slow-mapAsync }
|
|
||||||
|
|
||||||
The output may look like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
before: a
|
|
||||||
before: B
|
|
||||||
before: C
|
|
||||||
before: D
|
|
||||||
running: a (1)
|
|
||||||
running: B (2)
|
|
||||||
before: e
|
|
||||||
running: C (3)
|
|
||||||
before: F
|
|
||||||
running: D (4)
|
|
||||||
before: g
|
|
||||||
before: H
|
|
||||||
completed: C (3)
|
|
||||||
completed: B (2)
|
|
||||||
completed: D (1)
|
|
||||||
completed: a (0)
|
|
||||||
after: A
|
|
||||||
after: B
|
|
||||||
running: e (1)
|
|
||||||
after: C
|
|
||||||
after: D
|
|
||||||
running: F (2)
|
|
||||||
before: i
|
|
||||||
before: J
|
|
||||||
running: g (3)
|
|
||||||
running: H (4)
|
|
||||||
completed: H (2)
|
|
||||||
completed: F (3)
|
|
||||||
completed: e (1)
|
|
||||||
completed: g (0)
|
|
||||||
after: E
|
|
||||||
after: F
|
|
||||||
running: i (1)
|
|
||||||
after: G
|
|
||||||
after: H
|
|
||||||
running: J (2)
|
|
||||||
completed: J (1)
|
|
||||||
completed: i (0)
|
|
||||||
after: I
|
|
||||||
after: J
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that `after` lines are in the same order as the `before` lines even
|
|
||||||
though elements are `completed` in a different order. For example `H`
|
|
||||||
is `completed` before `g`, but still emitted afterwards.
|
|
||||||
|
|
||||||
The numbers in parenthesis illustrates how many calls that are in progress at
|
|
||||||
the same time. Here the downstream demand and thereby the number of concurrent
|
|
||||||
calls are limited by the buffer size (4) of the `ActorMaterializerSettings`.
|
|
||||||
|
|
||||||
Here is how we can use the same service with `mapAsyncUnordered`:
|
|
||||||
|
|
||||||
@@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #sometimes-slow-mapAsyncUnordered }
|
|
||||||
|
|
||||||
The output may look like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
before: a
|
|
||||||
before: B
|
|
||||||
before: C
|
|
||||||
before: D
|
|
||||||
running: a (1)
|
|
||||||
running: B (2)
|
|
||||||
before: e
|
|
||||||
running: C (3)
|
|
||||||
before: F
|
|
||||||
running: D (4)
|
|
||||||
before: g
|
|
||||||
before: H
|
|
||||||
completed: B (3)
|
|
||||||
completed: C (1)
|
|
||||||
completed: D (2)
|
|
||||||
after: B
|
|
||||||
after: D
|
|
||||||
running: e (2)
|
|
||||||
after: C
|
|
||||||
running: F (3)
|
|
||||||
before: i
|
|
||||||
before: J
|
|
||||||
completed: F (2)
|
|
||||||
after: F
|
|
||||||
running: g (3)
|
|
||||||
running: H (4)
|
|
||||||
completed: H (3)
|
|
||||||
after: H
|
|
||||||
completed: a (2)
|
|
||||||
after: A
|
|
||||||
running: i (3)
|
|
||||||
running: J (4)
|
|
||||||
completed: J (3)
|
|
||||||
after: J
|
|
||||||
completed: e (2)
|
|
||||||
after: E
|
|
||||||
completed: g (1)
|
|
||||||
after: G
|
|
||||||
completed: i (0)
|
|
||||||
after: I
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that `after` lines are not in the same order as the `before` lines. For example
|
|
||||||
`H` overtakes the slow `G`.
|
|
||||||
|
|
||||||
The numbers in parenthesis illustrates how many calls that are in progress at
|
|
||||||
the same time. Here the downstream demand and thereby the number of concurrent
|
|
||||||
calls are limited by the buffer size (4) of the `ActorMaterializerSettings`.
|
|
||||||
|
|
||||||
<a id="reactive-streams-integration"></a>
|
|
||||||
## Integrating with Reactive Streams
|
|
||||||
|
|
||||||
[Reactive Streams](http://reactive-streams.org/) defines a standard for asynchronous stream processing with non-blocking
|
|
||||||
back pressure. It makes it possible to plug together stream libraries that adhere to the standard.
|
|
||||||
Akka Streams is one such library.
|
|
||||||
|
|
||||||
An incomplete list of other implementations:
|
|
||||||
|
|
||||||
* [Reactor (1.1+)](https://github.com/reactor/reactor)
|
|
||||||
* [RxJava](https://github.com/ReactiveX/RxJavaReactiveStreams)
|
|
||||||
* [Ratpack](http://www.ratpack.io/manual/current/streams.html)
|
|
||||||
* [Slick](http://slick.lightbend.com)
|
|
||||||
|
|
||||||
The two most important interfaces in Reactive Streams are the `Publisher` and `Subscriber`.
|
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #imports }
|
|
||||||
|
|
||||||
Let us assume that a library provides a publisher of tweets:
|
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #tweets-publisher }
|
|
||||||
|
|
||||||
and another library knows how to store author handles in a database:
|
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #author-storage-subscriber }
|
|
||||||
|
|
||||||
Using an Akka Streams `Flow` we can transform the stream and connect those:
|
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #authors #connect-all }
|
|
||||||
|
|
||||||
The `Publisher` is used as an input `Source` to the flow and the
|
|
||||||
`Subscriber` is used as an output `Sink`.
|
|
||||||
|
|
||||||
A `Flow` can also be also converted to a `RunnableGraph[Processor[In, Out]]` which
|
|
||||||
materializes to a `Processor` when `run()` is called. `run()` itself can be called multiple
|
|
||||||
times, resulting in a new `Processor` instance each time.
|
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #flow-publisher-subscriber }
|
|
||||||
|
|
||||||
A publisher can be connected to a subscriber with the `subscribe` method.
|
|
||||||
|
|
||||||
It is also possible to expose a `Source` as a `Publisher`
|
|
||||||
by using the Publisher-`Sink`:
|
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #source-publisher }
|
|
||||||
|
|
||||||
A publisher that is created with `Sink.asPublisher(AsPublisher.WITHOUT_FANOUT)` supports only a single subscription.
|
|
||||||
Additional subscription attempts will be rejected with an `IllegalStateException`.
|
|
||||||
|
|
||||||
A publisher that supports multiple subscribers using fan-out/broadcasting is created as follows:
|
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #author-alert-subscriber #author-storage-subscriber }
|
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #source-fanoutPublisher }
|
|
||||||
|
|
||||||
The input buffer size of the stage controls how far apart the slowest subscriber can be from the fastest subscriber
|
|
||||||
before slowing down the stream.
|
|
||||||
|
|
||||||
To make the picture complete, it is also possible to expose a `Sink` as a `Subscriber`
|
|
||||||
by using the Subscriber-`Source`:
|
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #sink-subscriber }
|
|
||||||
|
|
||||||
It is also possible to use re-wrap `Processor` instances as a `Flow` by
|
|
||||||
passing a factory function that will create the `Processor` instances:
|
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #use-processor }
|
|
||||||
|
|
||||||
Please note that a factory is necessary to achieve reusability of the resulting `Flow`.
|
|
||||||
|
|
||||||
### Implementing Reactive Streams Publisher or Subscriber
|
|
||||||
|
|
||||||
As described above any Akka Streams `Source` can be exposed as a Reactive Streams `Publisher` and
|
|
||||||
any `Sink` can be exposed as a Reactive Streams `Subscriber`. Therefore we recommend that you
|
|
||||||
implement Reactive Streams integrations with built-in stages or @ref:[custom stages](stream-customize.md).
|
|
||||||
|
|
||||||
For historical reasons the `ActorPublisher` and `ActorSubscriber` traits are
|
|
||||||
provided to support implementing Reactive Streams `Publisher` and `Subscriber` with
|
|
||||||
an `Actor`.
|
|
||||||
|
|
||||||
These can be consumed by other Reactive Stream libraries or used as an Akka Streams `Source` or `Sink`.
|
|
||||||
|
|
||||||
@@@ warning
|
|
||||||
|
|
||||||
`ActorPublisher` and `ActorSubscriber` will probably be deprecated in future versions of Akka.
|
|
||||||
|
|
||||||
@@@
|
|
||||||
|
|
||||||
@@@ warning
|
|
||||||
|
|
||||||
`ActorPublisher` and `ActorSubscriber` cannot be used with remote actors,
|
|
||||||
because if signals of the Reactive Streams protocol (e.g. `request`) are lost the
|
|
||||||
the stream may deadlock.
|
|
||||||
|
|
||||||
@@@
|
|
||||||
|
|
||||||
#### ActorPublisher
|
|
||||||
|
|
||||||
@@@ warning
|
|
||||||
|
|
||||||
**Deprecation warning:** `ActorPublisher` is deprecated in favour of the vastly more
|
|
||||||
type-safe and safe to implement `akka.stream.stage.GraphStage`. It can also
|
|
||||||
expose a "stage actor ref" is needed to be addressed as-if an Actor.
|
|
||||||
Custom stages implemented using `GraphStage` are also automatically fusable.
|
|
||||||
|
|
||||||
To learn more about implementing custom stages using it refer to @ref:[Custom processing with GraphStage](stream-customize.md#graphstage).
|
|
||||||
|
|
||||||
@@@
|
|
||||||
|
|
||||||
Extend `akka.stream.actor.AbstractActorPublisher` to implement a
|
|
||||||
stream publisher that keeps track of the subscription life cycle and requested elements.
|
|
||||||
|
|
||||||
Here is an example of such an actor. It dispatches incoming jobs to the attached subscriber:
|
|
||||||
|
|
||||||
@@snip [ActorPublisherDocTest.java]($code$/java/jdocs/stream/ActorPublisherDocTest.java) { #job-manager }
|
|
||||||
|
|
||||||
You send elements to the stream by calling `onNext`. You are allowed to send as many
|
|
||||||
elements as have been requested by the stream subscriber. This amount can be inquired with
|
|
||||||
`totalDemand`. It is only allowed to use `onNext` when `isActive` and `totalDemand>0`,
|
|
||||||
otherwise `onNext` will throw `IllegalStateException`.
|
|
||||||
|
|
||||||
When the stream subscriber requests more elements the `ActorPublisherMessage.Request` message
|
|
||||||
is delivered to this actor, and you can act on that event. The `totalDemand`
|
|
||||||
is updated automatically.
|
|
||||||
|
|
||||||
When the stream subscriber cancels the subscription the `ActorPublisherMessage.Cancel` message
|
|
||||||
is delivered to this actor. After that subsequent calls to `onNext` will be ignored.
|
|
||||||
|
|
||||||
You can complete the stream by calling `onComplete`. After that you are not allowed to
|
|
||||||
call `onNext`, `onError` and `onComplete`.
|
|
||||||
|
|
||||||
You can terminate the stream with failure by calling `onError`. After that you are not allowed to
|
|
||||||
call `onNext`, `onError` and `onComplete`.
|
|
||||||
|
|
||||||
If you suspect that this `AbstractActorPublisher` may never get subscribed to, you can override the `subscriptionTimeout`
|
|
||||||
method to provide a timeout after which this Publisher should be considered canceled. The actor will be notified when
|
|
||||||
the timeout triggers via an `ActorPublisherMessage.SubscriptionTimeoutExceeded` message and MUST then perform
|
|
||||||
cleanup and stop itself.
|
|
||||||
|
|
||||||
If the actor is stopped the stream will be completed, unless it was not already terminated with
|
|
||||||
failure, completed or canceled.
|
|
||||||
|
|
||||||
More detailed information can be found in the API documentation.
|
|
||||||
|
|
||||||
This is how it can be used as input `Source` to a `Flow`:
|
|
||||||
|
|
||||||
@@snip [ActorPublisherDocTest.java]($code$/java/jdocs/stream/ActorPublisherDocTest.java) { #actor-publisher-usage }
|
|
||||||
|
|
||||||
You can only attach one subscriber to this publisher. Use a `Broadcast`-element or
|
|
||||||
attach a `Sink.asPublisher(AsPublisher.WITH_FANOUT)` to enable multiple subscribers.
|
|
||||||
|
|
||||||
#### ActorSubscriber
|
|
||||||
|
|
||||||
@@@ warning
|
|
||||||
|
|
||||||
**Deprecation warning:** `ActorSubscriber` is deprecated in favour of the vastly more
|
|
||||||
type-safe and safe to implement `akka.stream.stage.GraphStage`. It can also
|
|
||||||
expose a "stage actor ref" is needed to be addressed as-if an Actor.
|
|
||||||
Custom stages implemented using `GraphStage` are also automatically fusable.
|
|
||||||
|
|
||||||
To learn more about implementing custom stages using it refer to @ref:[Custom processing with GraphStage](../stream/stream-customize.md#graphstage).
|
|
||||||
|
|
||||||
@@@
|
|
||||||
|
|
||||||
Extend `akka.stream.actor.AbstractActorSubscriber` to make your class a stream subscriber with
|
|
||||||
full control of stream back pressure. It will receive
|
|
||||||
`ActorSubscriberMessage.OnNext`, `ActorSubscriberMessage.OnComplete` and `ActorSubscriberMessage.OnError`
|
|
||||||
messages from the stream. It can also receive other, non-stream messages, in the same way as any actor.
|
|
||||||
|
|
||||||
Here is an example of such an actor. It dispatches incoming jobs to child worker actors:
|
|
||||||
|
|
||||||
@@snip [ActorSubscriberDocTest.java]($code$/java/jdocs/stream/ActorSubscriberDocTest.java) { #worker-pool }
|
|
||||||
|
|
||||||
Subclass must define the `RequestStrategy` to control stream back pressure.
|
|
||||||
After each incoming message the `AbstractActorSubscriber` will automatically invoke
|
|
||||||
the `RequestStrategy.requestDemand` and propagate the returned demand to the stream.
|
|
||||||
|
|
||||||
* The provided `WatermarkRequestStrategy` is a good strategy if the actor performs work itself.
|
|
||||||
* The provided `MaxInFlightRequestStrategy` is useful if messages are queued internally or
|
|
||||||
delegated to other actors.
|
|
||||||
* You can also implement a custom `RequestStrategy` or call `request` manually together with
|
|
||||||
`ZeroRequestStrategy` or some other strategy. In that case
|
|
||||||
you must also call `request` when the actor is started or when it is ready, otherwise
|
|
||||||
it will not receive any elements.
|
|
||||||
|
|
||||||
More detailed information can be found in the API documentation.
|
|
||||||
|
|
||||||
This is how it can be used as output `Sink` to a `Flow`:
|
|
||||||
|
|
||||||
@@snip [ActorSubscriberDocTest.java]($code$/java/jdocs/stream/ActorSubscriberDocTest.java) { #actor-subscriber-usage }
|
|
||||||
1
akka-docs/src/main/paradox/java/stream/stream-integrations.md
Symbolic link
1
akka-docs/src/main/paradox/java/stream/stream-integrations.md
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../scala/stream/stream-integrations.md
|
||||||
|
|
@ -12,10 +12,14 @@ materialized by `Source.actorRef`.
|
||||||
|
|
||||||
A nice way to delegate some processing of elements in a stream to an actor is to
|
A nice way to delegate some processing of elements in a stream to an actor is to
|
||||||
use `ask` in `mapAsync`. The back-pressure of the stream is maintained by
|
use `ask` in `mapAsync`. The back-pressure of the stream is maintained by
|
||||||
the `Future` of the `ask` and the mailbox of the actor will not be filled with
|
the @scala[`Future`]@java[`CompletionStage`] of the `ask` and the mailbox of the actor will not be filled with
|
||||||
more messages than the given `parallelism` of the `mapAsync` stage.
|
more messages than the given `parallelism` of the `mapAsync` stage.
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #mapAsync-ask }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #mapAsync-ask }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #mapAsync-ask }
|
||||||
|
|
||||||
Note that the messages received in the actor will be in the same order as
|
Note that the messages received in the actor will be in the same order as
|
||||||
the stream elements, i.e. the `parallelism` does not change the ordering
|
the stream elements, i.e. the `parallelism` does not change the ordering
|
||||||
|
|
@ -24,24 +28,28 @@ even though the actor will only process one message at a time because then there
|
||||||
is already a message in the mailbox when the actor has completed previous
|
is already a message in the mailbox when the actor has completed previous
|
||||||
message.
|
message.
|
||||||
|
|
||||||
The actor must reply to the `sender()` for each message from the stream. That
|
The actor must reply to the @scala[`sender()`]@java[`getSender()`] for each message from the stream. That
|
||||||
reply will complete the `Future` of the `ask` and it will be the element that
|
reply will complete the @scala[`Future`]@java[`CompletionStage`] of the `ask` and it will be the element that
|
||||||
is emitted downstreams from `mapAsync`.
|
is emitted downstreams from `mapAsync`.
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #ask-actor }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #ask-actor }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #ask-actor }
|
||||||
|
|
||||||
The stream can be completed with failure by sending `akka.actor.Status.Failure`
|
The stream can be completed with failure by sending `akka.actor.Status.Failure`
|
||||||
as reply from the actor.
|
as reply from the actor.
|
||||||
|
|
||||||
If the `ask` fails due to timeout the stream will be completed with
|
If the `ask` fails due to timeout the stream will be completed with
|
||||||
`TimeoutException` failure. If that is not desired outcome you can use `recover`
|
`TimeoutException` failure. If that is not desired outcome you can use `recover`
|
||||||
on the `ask` `Future`.
|
on the `ask` @scala[`Future`]@java[`CompletionStage`].
|
||||||
|
|
||||||
If you don't care about the reply values and only use them as back-pressure signals you
|
If you don't care about the reply values and only use them as back-pressure signals you
|
||||||
can use `Sink.ignore` after the `mapAsync` stage and then actor is effectively a sink
|
can use `Sink.ignore` after the `mapAsync` stage and then actor is effectively a sink
|
||||||
of the stream.
|
of the stream.
|
||||||
|
|
||||||
The same pattern can be used with @ref:[Actor routers](../routing.md). Then you
|
The same pattern can be used with @ref[Actor routers](../routing.md). Then you
|
||||||
can use `mapAsyncUnordered` for better efficiency if you don't care about the
|
can use `mapAsyncUnordered` for better efficiency if you don't care about the
|
||||||
order of the emitted downstream elements (the replies).
|
order of the emitted downstream elements (the replies).
|
||||||
|
|
||||||
|
|
@ -76,12 +84,12 @@ be buffered until request for demand is received.
|
||||||
Use overflow strategy `akka.stream.OverflowStrategy.backpressure` to avoid dropping of elements if the
|
Use overflow strategy `akka.stream.OverflowStrategy.backpressure` to avoid dropping of elements if the
|
||||||
buffer is full.
|
buffer is full.
|
||||||
|
|
||||||
`SourceQueue.offer` returns `Future[QueueOfferResult]` which completes with `QueueOfferResult.Enqueued`
|
`SourceQueue.offer` returns @scala[`Future[QueueOfferResult]`]@java[`CompletionStage<QueueOfferResult>`] which completes with `QueueOfferResult.Enqueued`
|
||||||
if element was added to buffer or sent downstream. It completes with `QueueOfferResult.Dropped` if element
|
if element was added to buffer or sent downstream. It completes with `QueueOfferResult.Dropped` if element
|
||||||
was dropped. Can also complete with `QueueOfferResult.Failure` - when stream failed or
|
was dropped. Can also complete with `QueueOfferResult.Failure` - when stream failed or
|
||||||
`QueueOfferResult.QueueClosed` when downstream is completed.
|
`QueueOfferResult.QueueClosed` when downstream is completed.
|
||||||
|
|
||||||
When used from an actor you typically `pipe` the result of the `Future` back to the actor to
|
When used from an actor you typically `pipe` the result of the @scala[`Future`]@java[`CompletionStage`] back to the actor to
|
||||||
continue processing.
|
continue processing.
|
||||||
|
|
||||||
### Source.actorRef
|
### Source.actorRef
|
||||||
|
|
@ -113,27 +121,47 @@ performed with `mapAsync` or `mapAsyncUnordered`.
|
||||||
For example, sending emails to the authors of selected tweets using an external
|
For example, sending emails to the authors of selected tweets using an external
|
||||||
email service:
|
email service:
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #email-server-send }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #email-server-send }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #email-server-send }
|
||||||
|
|
||||||
We start with the tweet stream of authors:
|
We start with the tweet stream of authors:
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #tweet-authors }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #tweet-authors}
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #tweet-authors }
|
||||||
|
|
||||||
Assume that we can lookup their email address using:
|
Assume that we can lookup their email address using:
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #email-address-lookup }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #email-address-lookup }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #email-address-lookup }
|
||||||
|
|
||||||
Transforming the stream of authors to a stream of email addresses by using the `lookupEmail`
|
Transforming the stream of authors to a stream of email addresses by using the `lookupEmail`
|
||||||
service can be done with `mapAsync`:
|
service can be done with `mapAsync`:
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #email-addresses-mapAsync }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #email-addresses-mapAsync }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #email-addresses-mapAsync }
|
||||||
|
|
||||||
Finally, sending the emails:
|
Finally, sending the emails:
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #send-emails }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #send-emails }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #send-emails }
|
||||||
|
|
||||||
`mapAsync` is applying the given function that is calling out to the external service to
|
`mapAsync` is applying the given function that is calling out to the external service to
|
||||||
each of the elements as they pass through this processing step. The function returns a `Future`
|
each of the elements as they pass through this processing step. The function returns a @scala[`Future`]@java[`CompletionStage`]
|
||||||
and the value of that future will be emitted downstreams. The number of Futures
|
and the value of that future will be emitted downstreams. The number of Futures
|
||||||
that shall run in parallel is given as the first argument to `mapAsync`.
|
that shall run in parallel is given as the first argument to `mapAsync`.
|
||||||
These Futures may complete in any order, but the elements that are emitted
|
These Futures may complete in any order, but the elements that are emitted
|
||||||
|
|
@ -152,14 +180,22 @@ result stream onwards for further processing or storage.
|
||||||
Note that `mapAsync` preserves the order of the stream elements. In this example the order
|
Note that `mapAsync` preserves the order of the stream elements. In this example the order
|
||||||
is not important and then we can use the more efficient `mapAsyncUnordered`:
|
is not important and then we can use the more efficient `mapAsyncUnordered`:
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #external-service-mapAsyncUnordered }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #external-service-mapAsyncUnordered }
|
||||||
|
|
||||||
In the above example the services conveniently returned a `Future` of the result.
|
Java
|
||||||
If that is not the case you need to wrap the call in a `Future`. If the service call
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #external-service-mapAsyncUnordered }
|
||||||
|
|
||||||
|
In the above example the services conveniently returned a @scala[`Future`]@java[`CompletionStage`] of the result.
|
||||||
|
If that is not the case you need to wrap the call in a @scala[`Future`]@java[`CompletionStage`]. If the service call
|
||||||
involves blocking you must also make sure that you run it on a dedicated execution context, to
|
involves blocking you must also make sure that you run it on a dedicated execution context, to
|
||||||
avoid starvation and disturbance of other tasks in the system.
|
avoid starvation and disturbance of other tasks in the system.
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #blocking-mapAsync }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #blocking-mapAsync }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #blocking-mapAsync }
|
||||||
|
|
||||||
The configuration of the `"blocking-dispatcher"` may look something like:
|
The configuration of the `"blocking-dispatcher"` may look something like:
|
||||||
|
|
||||||
|
|
@ -168,7 +204,11 @@ The configuration of the `"blocking-dispatcher"` may look something like:
|
||||||
An alternative for blocking calls is to perform them in a `map` operation, still using a
|
An alternative for blocking calls is to perform them in a `map` operation, still using a
|
||||||
dedicated dispatcher for that operation.
|
dedicated dispatcher for that operation.
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #blocking-map }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #blocking-map }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #blocking-map }
|
||||||
|
|
||||||
However, that is not exactly the same as `mapAsync`, since the `mapAsync` may run
|
However, that is not exactly the same as `mapAsync`, since the `mapAsync` may run
|
||||||
several calls concurrently, but `map` performs them one at a time.
|
several calls concurrently, but `map` performs them one at a time.
|
||||||
|
|
@ -176,10 +216,14 @@ several calls concurrently, but `map` performs them one at a time.
|
||||||
For a service that is exposed as an actor, or if an actor is used as a gateway in front of an
|
For a service that is exposed as an actor, or if an actor is used as a gateway in front of an
|
||||||
external service, you can use `ask`:
|
external service, you can use `ask`:
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #save-tweets }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #save-tweets }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #save-tweets }
|
||||||
|
|
||||||
Note that if the `ask` is not completed within the given timeout the stream is completed with failure.
|
Note that if the `ask` is not completed within the given timeout the stream is completed with failure.
|
||||||
If that is not desired outcome you can use `recover` on the `ask` `Future`.
|
If that is not desired outcome you can use `recover` on the `ask` @scala[`Future`]@java[`CompletionStage`].
|
||||||
|
|
||||||
### Illustrating ordering and parallelism
|
### Illustrating ordering and parallelism
|
||||||
|
|
||||||
|
|
@ -204,14 +248,22 @@ successive calls as long as there is downstream demand of several elements.
|
||||||
|
|
||||||
Here is a fictive service that we can use to illustrate these aspects.
|
Here is a fictive service that we can use to illustrate these aspects.
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #sometimes-slow-service }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #sometimes-slow-service }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #sometimes-slow-service }
|
||||||
|
|
||||||
Elements starting with a lower case character are simulated to take longer time
|
Elements starting with a lower case character are simulated to take longer time
|
||||||
to process.
|
to process.
|
||||||
|
|
||||||
Here is how we can use it with `mapAsync`:
|
Here is how we can use it with `mapAsync`:
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #sometimes-slow-mapAsync }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #sometimes-slow-mapAsync }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #sometimes-slow-mapAsync }
|
||||||
|
|
||||||
The output may look like this:
|
The output may look like this:
|
||||||
|
|
||||||
|
|
@ -268,7 +320,11 @@ calls are limited by the buffer size (4) of the `ActorMaterializerSettings`.
|
||||||
|
|
||||||
Here is how we can use the same service with `mapAsyncUnordered`:
|
Here is how we can use the same service with `mapAsyncUnordered`:
|
||||||
|
|
||||||
@@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #sometimes-slow-mapAsyncUnordered }
|
Scala
|
||||||
|
: @@snip [IntegrationDocSpec.scala]($code$/scala/docs/stream/IntegrationDocSpec.scala) { #sometimes-slow-mapAsyncUnordered }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [IntegrationDocTest.java]($code$/java/jdocs/stream/IntegrationDocTest.java) { #sometimes-slow-mapAsyncUnordered }
|
||||||
|
|
||||||
The output may look like this:
|
The output may look like this:
|
||||||
|
|
||||||
|
|
@ -338,19 +394,35 @@ An incomplete list of other implementations:
|
||||||
|
|
||||||
The two most important interfaces in Reactive Streams are the `Publisher` and `Subscriber`.
|
The two most important interfaces in Reactive Streams are the `Publisher` and `Subscriber`.
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #imports }
|
Scala
|
||||||
|
: @@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #imports }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #imports }
|
||||||
|
|
||||||
Let us assume that a library provides a publisher of tweets:
|
Let us assume that a library provides a publisher of tweets:
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #tweets-publisher }
|
Scala
|
||||||
|
: @@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #tweets-publisher }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #tweets-publisher }
|
||||||
|
|
||||||
and another library knows how to store author handles in a database:
|
and another library knows how to store author handles in a database:
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #author-storage-subscriber }
|
Scala
|
||||||
|
: @@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #author-storage-subscriber }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #author-storage-subscriber }
|
||||||
|
|
||||||
Using an Akka Streams `Flow` we can transform the stream and connect those:
|
Using an Akka Streams `Flow` we can transform the stream and connect those:
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #authors #connect-all }
|
Scala
|
||||||
|
: @@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #authors #connect-all }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #authors #connect-all }
|
||||||
|
|
||||||
The `Publisher` is used as an input `Source` to the flow and the
|
The `Publisher` is used as an input `Source` to the flow and the
|
||||||
`Subscriber` is used as an output `Sink`.
|
`Subscriber` is used as an output `Sink`.
|
||||||
|
|
@ -359,23 +431,40 @@ A `Flow` can also be also converted to a `RunnableGraph[Processor[In, Out]]` whi
|
||||||
materializes to a `Processor` when `run()` is called. `run()` itself can be called multiple
|
materializes to a `Processor` when `run()` is called. `run()` itself can be called multiple
|
||||||
times, resulting in a new `Processor` instance each time.
|
times, resulting in a new `Processor` instance each time.
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #flow-publisher-subscriber }
|
Scala
|
||||||
|
: @@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #flow-publisher-subscriber }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #flow-publisher-subscriber }
|
||||||
|
|
||||||
A publisher can be connected to a subscriber with the `subscribe` method.
|
A publisher can be connected to a subscriber with the `subscribe` method.
|
||||||
|
|
||||||
It is also possible to expose a `Source` as a `Publisher`
|
It is also possible to expose a `Source` as a `Publisher`
|
||||||
by using the Publisher-`Sink`:
|
by using the Publisher-`Sink`:
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #source-publisher }
|
Scala
|
||||||
|
: @@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #source-publisher }
|
||||||
|
|
||||||
A publisher that is created with `Sink.asPublisher(fanout = false)` supports only a single subscription.
|
Java
|
||||||
|
: @@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #source-publisher }
|
||||||
|
|
||||||
|
A publisher that is created with @scala[`Sink.asPublisher(fanout = false)`]@java[`Sink.asPublisher(AsPublisher.WITHOUT_FANOUT)`] supports only a single subscription.
|
||||||
Additional subscription attempts will be rejected with an `IllegalStateException`.
|
Additional subscription attempts will be rejected with an `IllegalStateException`.
|
||||||
|
|
||||||
A publisher that supports multiple subscribers using fan-out/broadcasting is created as follows:
|
A publisher that supports multiple subscribers using fan-out/broadcasting is created as follows:
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #author-alert-subscriber #author-storage-subscriber }
|
Scala
|
||||||
|
: @@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #author-alert-subscriber #author-storage-subscriber }
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #source-fanoutPublisher }
|
Java
|
||||||
|
: @@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #author-alert-subscriber #author-storage-subscriber }
|
||||||
|
|
||||||
|
|
||||||
|
Scala
|
||||||
|
: @@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #source-fanoutPublisher }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #source-fanoutPublisher }
|
||||||
|
|
||||||
The input buffer size of the stage controls how far apart the slowest subscriber can be from the fastest subscriber
|
The input buffer size of the stage controls how far apart the slowest subscriber can be from the fastest subscriber
|
||||||
before slowing down the stream.
|
before slowing down the stream.
|
||||||
|
|
@ -383,12 +472,20 @@ before slowing down the stream.
|
||||||
To make the picture complete, it is also possible to expose a `Sink` as a `Subscriber`
|
To make the picture complete, it is also possible to expose a `Sink` as a `Subscriber`
|
||||||
by using the Subscriber-`Source`:
|
by using the Subscriber-`Source`:
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #sink-subscriber }
|
Scala
|
||||||
|
: @@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #sink-subscriber }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #sink-subscriber }
|
||||||
|
|
||||||
It is also possible to use re-wrap `Processor` instances as a `Flow` by
|
It is also possible to use re-wrap `Processor` instances as a `Flow` by
|
||||||
passing a factory function that will create the `Processor` instances:
|
passing a factory function that will create the `Processor` instances:
|
||||||
|
|
||||||
@@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #use-processor }
|
Scala
|
||||||
|
: @@snip [ReactiveStreamsDocSpec.scala]($code$/scala/docs/stream/ReactiveStreamsDocSpec.scala) { #use-processor }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [ReactiveStreamsDocTest.java]($code$/java/jdocs/stream/ReactiveStreamsDocTest.java) { #use-processor }
|
||||||
|
|
||||||
Please note that a factory is necessary to achieve reusability of the resulting `Flow`.
|
Please note that a factory is necessary to achieve reusability of the resulting `Flow`.
|
||||||
|
|
||||||
|
|
@ -396,7 +493,7 @@ Please note that a factory is necessary to achieve reusability of the resulting
|
||||||
|
|
||||||
As described above any Akka Streams `Source` can be exposed as a Reactive Streams `Publisher` and
|
As described above any Akka Streams `Source` can be exposed as a Reactive Streams `Publisher` and
|
||||||
any `Sink` can be exposed as a Reactive Streams `Subscriber`. Therefore we recommend that you
|
any `Sink` can be exposed as a Reactive Streams `Subscriber`. Therefore we recommend that you
|
||||||
implement Reactive Streams integrations with built-in stages or @ref:[custom stages](stream-customize.md).
|
implement Reactive Streams integrations with built-in stages or @ref[custom stages](stream-customize.md).
|
||||||
|
|
||||||
For historical reasons the `ActorPublisher` and `ActorSubscriber` traits are
|
For historical reasons the `ActorPublisher` and `ActorSubscriber` traits are
|
||||||
provided to support implementing Reactive Streams `Publisher` and `Subscriber` with
|
provided to support implementing Reactive Streams `Publisher` and `Subscriber` with
|
||||||
|
|
@ -427,16 +524,20 @@ type-safe and safe to implement `akka.stream.stage.GraphStage`. It can also
|
||||||
expose a "stage actor ref" is needed to be addressed as-if an Actor.
|
expose a "stage actor ref" is needed to be addressed as-if an Actor.
|
||||||
Custom stages implemented using `GraphStage` are also automatically fusable.
|
Custom stages implemented using `GraphStage` are also automatically fusable.
|
||||||
|
|
||||||
To learn more about implementing custom stages using it refer to @ref:[Custom processing with GraphStage](stream-customize.md#graphstage).
|
To learn more about implementing custom stages using it refer to @ref[Custom processing with GraphStage](stream-customize.md#graphstage).
|
||||||
|
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
Extend/mixin `akka.stream.actor.ActorPublisher` in your `Actor` to make it a
|
Extend @scala[`akka.stream.actor.ActorPublisher` in your `Actor` to make it]@java[`akka.stream.actor.AbstractActorPublisher` to implement] a
|
||||||
stream publisher that keeps track of the subscription life cycle and requested elements.
|
stream publisher that keeps track of the subscription life cycle and requested elements.
|
||||||
|
|
||||||
Here is an example of such an actor. It dispatches incoming jobs to the attached subscriber:
|
Here is an example of such an actor. It dispatches incoming jobs to the attached subscriber:
|
||||||
|
|
||||||
@@snip [ActorPublisherDocSpec.scala]($code$/scala/docs/stream/ActorPublisherDocSpec.scala) { #job-manager }
|
Scala
|
||||||
|
: @@snip [ActorPublisherDocSpec.scala]($code$/scala/docs/stream/ActorPublisherDocSpec.scala) { #job-manager }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [ActorPublisherDocTest.java]($code$/java/jdocs/stream/ActorPublisherDocTest.java) { #job-manager }
|
||||||
|
|
||||||
You send elements to the stream by calling `onNext`. You are allowed to send as many
|
You send elements to the stream by calling `onNext`. You are allowed to send as many
|
||||||
elements as have been requested by the stream subscriber. This amount can be inquired with
|
elements as have been requested by the stream subscriber. This amount can be inquired with
|
||||||
|
|
@ -456,7 +557,7 @@ call `onNext`, `onError` and `onComplete`.
|
||||||
You can terminate the stream with failure by calling `onError`. After that you are not allowed to
|
You can terminate the stream with failure by calling `onError`. After that you are not allowed to
|
||||||
call `onNext`, `onError` and `onComplete`.
|
call `onNext`, `onError` and `onComplete`.
|
||||||
|
|
||||||
If you suspect that this `ActorPublisher` may never get subscribed to, you can override the `subscriptionTimeout`
|
If you suspect that this @scala[`ActorPublisher`]@java[`AbstractActorPublisher`] may never get subscribed to, you can override the `subscriptionTimeout`
|
||||||
method to provide a timeout after which this Publisher should be considered canceled. The actor will be notified when
|
method to provide a timeout after which this Publisher should be considered canceled. The actor will be notified when
|
||||||
the timeout triggers via an `ActorPublisherMessage.SubscriptionTimeoutExceeded` message and MUST then perform
|
the timeout triggers via an `ActorPublisherMessage.SubscriptionTimeoutExceeded` message and MUST then perform
|
||||||
cleanup and stop itself.
|
cleanup and stop itself.
|
||||||
|
|
@ -468,10 +569,17 @@ More detailed information can be found in the API documentation.
|
||||||
|
|
||||||
This is how it can be used as input `Source` to a `Flow`:
|
This is how it can be used as input `Source` to a `Flow`:
|
||||||
|
|
||||||
@@snip [ActorPublisherDocSpec.scala]($code$/scala/docs/stream/ActorPublisherDocSpec.scala) { #actor-publisher-usage }
|
Scala
|
||||||
|
: @@snip [ActorPublisherDocSpec.scala]($code$/scala/docs/stream/ActorPublisherDocSpec.scala) { #actor-publisher-usage }
|
||||||
|
|
||||||
A publisher that is created with `Sink.asPublisher` supports a specified number of subscribers. Additional
|
Java
|
||||||
subscription attempts will be rejected with an `IllegalStateException`.
|
: @@snip [ActorPublisherDocTest.java]($code$/java/jdocs/stream/ActorPublisherDocTest.java) { #actor-publisher-usage }
|
||||||
|
|
||||||
|
@scala[A publisher that is created with `Sink.asPublisher` supports a specified number of subscribers. Additional
|
||||||
|
subscription attempts will be rejected with an `IllegalStateException`.
|
||||||
|
]@java[You can only attach one subscriber to this publisher. Use a `Broadcast`-element or
|
||||||
|
attach a `Sink.asPublisher(AsPublisher.WITH_FANOUT)` to enable multiple subscribers.
|
||||||
|
]
|
||||||
|
|
||||||
#### ActorSubscriber
|
#### ActorSubscriber
|
||||||
|
|
||||||
|
|
@ -482,21 +590,25 @@ type-safe and safe to implement `akka.stream.stage.GraphStage`. It can also
|
||||||
expose a "stage actor ref" is needed to be addressed as-if an Actor.
|
expose a "stage actor ref" is needed to be addressed as-if an Actor.
|
||||||
Custom stages implemented using `GraphStage` are also automatically fusable.
|
Custom stages implemented using `GraphStage` are also automatically fusable.
|
||||||
|
|
||||||
To learn more about implementing custom stages using it refer to @ref:[Custom processing with GraphStage](stream-customize.md#graphstage).
|
To learn more about implementing custom stages using it refer to @ref[Custom processing with GraphStage](stream-customize.md#graphstage).
|
||||||
|
|
||||||
@@@
|
@@@
|
||||||
|
|
||||||
Extend/mixin `akka.stream.actor.ActorSubscriber` in your `Actor` to make it a
|
Extend @scala[`akka.stream.actor.ActorSubscriber` in your `Actor` to make it]@java[`akka.stream.actor.AbstractActorSubscriber` to make your class] a
|
||||||
stream subscriber with full control of stream back pressure. It will receive
|
stream subscriber with full control of stream back pressure. It will receive
|
||||||
`ActorSubscriberMessage.OnNext`, `ActorSubscriberMessage.OnComplete` and `ActorSubscriberMessage.OnError`
|
`ActorSubscriberMessage.OnNext`, `ActorSubscriberMessage.OnComplete` and `ActorSubscriberMessage.OnError`
|
||||||
messages from the stream. It can also receive other, non-stream messages, in the same way as any actor.
|
messages from the stream. It can also receive other, non-stream messages, in the same way as any actor.
|
||||||
|
|
||||||
Here is an example of such an actor. It dispatches incoming jobs to child worker actors:
|
Here is an example of such an actor. It dispatches incoming jobs to child worker actors:
|
||||||
|
|
||||||
@@snip [ActorSubscriberDocSpec.scala]($code$/scala/docs/stream/ActorSubscriberDocSpec.scala) { #worker-pool }
|
Scala
|
||||||
|
: @@snip [ActorSubscriberDocSpec.scala]($code$/scala/docs/stream/ActorSubscriberDocSpec.scala) { #worker-pool }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [ActorSubscriberDocTest.java]($code$/java/jdocs/stream/ActorSubscriberDocTest.java) { #worker-pool }
|
||||||
|
|
||||||
Subclass must define the `RequestStrategy` to control stream back pressure.
|
Subclass must define the `RequestStrategy` to control stream back pressure.
|
||||||
After each incoming message the `ActorSubscriber` will automatically invoke
|
After each incoming message the @scala[`ActorSubscriber`]@java[`AbstractActorSubscriber`] will automatically invoke
|
||||||
the `RequestStrategy.requestDemand` and propagate the returned demand to the stream.
|
the `RequestStrategy.requestDemand` and propagate the returned demand to the stream.
|
||||||
|
|
||||||
* The provided `WatermarkRequestStrategy` is a good strategy if the actor performs work itself.
|
* The provided `WatermarkRequestStrategy` is a good strategy if the actor performs work itself.
|
||||||
|
|
@ -511,4 +623,8 @@ More detailed information can be found in the API documentation.
|
||||||
|
|
||||||
This is how it can be used as output `Sink` to a `Flow`:
|
This is how it can be used as output `Sink` to a `Flow`:
|
||||||
|
|
||||||
@@snip [ActorSubscriberDocSpec.scala]($code$/scala/docs/stream/ActorSubscriberDocSpec.scala) { #actor-subscriber-usage }
|
Scala
|
||||||
|
: @@snip [ActorSubscriberDocSpec.scala]($code$/scala/docs/stream/ActorSubscriberDocSpec.scala) { #actor-subscriber-usage }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [ActorSubscriberDocTest.java]($code$/java/jdocs/stream/ActorSubscriberDocTest.java) { #actor-subscriber-usage }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue