それぞれSource.actorPublisher()
メソッドとSink.actorSubscriber()
メソッドを使用して、アクターからソースとシンクを作成できます。しかし、アクターからFlow
を作成することは可能ですか?
概念的には、ActorPublisher
とActorSubscriber
の両方の特性を実装することを考えると、そうしない正当な理由はないようですが、残念ながら、Flow
オブジェクトには何もありません。これを行う方法。 this 優れたブログ投稿では、Akka Streamsの以前のバージョンで行われているため、最新(2.4.9)バージョンでも可能かどうかは疑問です。
私はAkkaチームの一員です。この質問を使用して、未加工のReactive Streamsインターフェースに関するいくつかのことを明確にしたいと思います。これがあなたのお役に立てば幸いです。
最も注目すべきは、Akkaチームのブログに、Flowsを含むカスタムステージの構築に関する複数の投稿を近日公開する予定ですので、注目してください。
ActorPublisher/ActorSubscriberを使用しないでください
ActorPublisher
とActorSubscriber
を使用しないでください。レベルが低すぎるため、 Reactive Streams仕様 に違反するような方法で実装することになります。それらは過去の遺物であり、それでも「パワーユーザーモードのみ」でした。今日、これらのクラスを使用する理由は本当にありません。フローを構築する方法を提供したことはありません。「生の」アクターAPIとして公開されて複雑になった場合、単純に爆発するので、 すべてのルールが正しく実装された です。
生のReactiveStreamsインターフェースを本当に実装したい場合は、 Specification's TCK を使用して、実装が正しいことを確認してください。 Flow
(またはRS用語ではProcessor
が処理する必要がある)のより複雑なコーナーケースのいくつかに不意を突かれる可能性があります。
ほとんどの操作は、低レベルにせずに構築できます
多くのフローは、単に例として_Flow[T]
_からビルドし、それに必要な操作を追加することで簡単にビルドできるはずです。
_val newFlow: Flow[String, Int, NotUsed] = Flow[String].map(_.toInt)
_
これは、フローの再利用可能な説明です。
パワーユーザーモードについて質問しているので、これはDSL自体で最も強力な演算子です:statefulFlatMapConcat
。プレーンストリーム要素を操作する操作の大部分は、それを使用して表現できます:Flow.statefulMapConcat[T](f: () ⇒ (Out) ⇒ Iterable[T]): Repr[T]
。
タイマーが必要な場合は、Zip
で_Source.timer
_などを使用できます。
GraphStageは、the最も単純で、safestカスタムステージを構築するためのAPIです
代わりに、Sources/Flows/Sinksの構築には、強力なおよび安全なAPIであるGraphStage
があります。 カスタムGraphStagesの構築に関するドキュメント (Sink/Source/Flowまたはany任意の形状)をお読みください。複雑なリアクティブストリームルールをすべて処理すると同時に、ステージ(フローなど)の実装中に完全な自由とタイプセーフを提供します。
たとえば、ドキュメントから取得したfilter(T => Boolean)
演算子のGraphStage実装です。
_class Filter[A](p: A => Boolean) extends GraphStage[FlowShape[A, A]] {
val in = Inlet[A]("Filter.in")
val out = Outlet[A]("Filter.out")
val shape = FlowShape.of(in, out)
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
new GraphStageLogic(shape) {
setHandler(in, new InHandler {
override def onPush(): Unit = {
val elem = grab(in)
if (p(elem)) Push(out, elem)
else pull(in)
}
})
setHandler(out, new OutHandler {
override def onPull(): Unit = {
pull(in)
}
})
}
}
_
また、非同期チャネルを処理し、デフォルトで融合可能です。
これらのブログ投稿では、ドキュメントに加えて、このAPIがあらゆる形のカスタムステージを構築する聖杯である理由について詳しく説明しています。
Konradのソリューションは、アクターを利用するカスタムステージを作成する方法を示していますが、ほとんどの場合、それは少しやり過ぎだと思います。
通常、質問に応答できるアクターがいくつかあります。
val actorRef : ActorRef = ???
type Input = ???
type Output = ???
val queryActor : Input => Future[Output] =
(actorRef ? _) andThen (_.mapTo[Output])
これは、基本的な Flow
同時リクエストの最大数を受け取る機能で簡単に利用できます。
val actorQueryFlow : Int => Flow[Input, Output, _] =
(parallelism) => Flow[Input].mapAsync[Output](parallelism)(queryActor)
actorQueryFlow
を任意のストリームに統合できるようになりました...
グラフステージを使用して構築したソリューションを次に示します。アクターは、バックプレッシャーをかけるためにすべてのメッセージを確認する必要があります。ストリームが失敗/完了するとアクターに通知され、アクターが終了するとストリームが失敗します。これは、askを使用したくない場合に役立ちます。すべての入力メッセージに対応する出力メッセージがあるわけではない場合。
import akka.actor.{ActorRef, Status, Terminated}
import akka.stream._
import akka.stream.stage.{GraphStage, GraphStageLogic, InHandler, OutHandler}
object ActorRefBackpressureFlowStage {
case object StreamInit
case object StreamAck
case object StreamCompleted
case class StreamFailed(ex: Throwable)
case class StreamElementIn[A](element: A)
case class StreamElementOut[A](element: A)
}
/**
* Sends the elements of the stream to the given `ActorRef` that sends back back-pressure signal.
* First element is always `StreamInit`, then stream is waiting for acknowledgement message
* `ackMessage` from the given actor which means that it is ready to process
* elements. It also requires `ackMessage` message after each stream element
* to make backpressure work. Stream elements are wrapped inside `StreamElementIn(elem)` messages.
*
* The target actor can emit elements at any time by sending a `StreamElementOut(elem)` message, which will
* be emitted downstream when there is demand.
*
* If the target actor terminates the stage will fail with a WatchedActorTerminatedException.
* When the stream is completed successfully a `StreamCompleted` message
* will be sent to the destination actor.
* When the stream is completed with failure a `StreamFailed(ex)` message will be send to the destination actor.
*/
class ActorRefBackpressureFlowStage[In, Out](private val flowActor: ActorRef) extends GraphStage[FlowShape[In, Out]] {
import ActorRefBackpressureFlowStage._
val in: Inlet[In] = Inlet("ActorFlowIn")
val out: Outlet[Out] = Outlet("ActorFlowOut")
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) {
private lazy val self = getStageActor {
case (_, StreamAck) =>
if(firstPullReceived) {
if (!isClosed(in) && !hasBeenPulled(in)) {
pull(in)
}
} else {
pullOnFirstPullReceived = true
}
case (_, StreamElementOut(elemOut)) =>
val elem = elemOut.asInstanceOf[Out]
emit(out, elem)
case (_, Terminated(targetRef)) =>
failStage(new WatchedActorTerminatedException("ActorRefBackpressureFlowStage", targetRef))
case (actorRef, unexpected) =>
failStage(new IllegalStateException(s"Unexpected message: `$unexpected` received from actor `$actorRef`."))
}
var firstPullReceived: Boolean = false
var pullOnFirstPullReceived: Boolean = false
override def preStart(): Unit = {
//initialize stage actor and watch flow actor.
self.watch(flowActor)
tellFlowActor(StreamInit)
}
setHandler(in, new InHandler {
override def onPush(): Unit = {
val elementIn = grab(in)
tellFlowActor(StreamElementIn(elementIn))
}
override def onUpstreamFailure(ex: Throwable): Unit = {
tellFlowActor(StreamFailed(ex))
super.onUpstreamFailure(ex)
}
override def onUpstreamFinish(): Unit = {
tellFlowActor(StreamCompleted)
super.onUpstreamFinish()
}
})
setHandler(out, new OutHandler {
override def onPull(): Unit = {
if(!firstPullReceived) {
firstPullReceived = true
if(pullOnFirstPullReceived) {
if (!isClosed(in) && !hasBeenPulled(in)) {
pull(in)
}
}
}
}
override def onDownstreamFinish(): Unit = {
tellFlowActor(StreamCompleted)
super.onDownstreamFinish()
}
})
private def tellFlowActor(message: Any): Unit = {
flowActor.tell(message, self.ref)
}
}
override def shape: FlowShape[In, Out] = FlowShape(in, out)
}