![](/img/trans.png)
[英]Wrapping Pub-Sub Java API in Akka Streams Custom Graph Stage
[英]Akka streams: dealing with futures within graph stage
在akka流階段FlowShape[A, B]
,我需要對A執行的部分處理是使用使用A數據構建的查詢來保存/查詢數據存儲。 但是那個數據存儲驅動程序查詢給了我一個未來,我不知道如何最好地處理它(我的主要問題在這里)。
case class Obj(a: String, b: Int, c: String)
case class Foo(myobject: Obj, name: String)
case class Bar(st: String)
//
class SaveAndGetId extends GraphStage[FlowShape[Foo, Bar]] {
val dao = new DbDao // some dao with an async driver
override def createLogic(inheritedAttributes: Attributes) = new GraphStageLogic(shape) {
setHandlers(in, out, new InHandler with Outhandler {
override def onPush() = {
val foo = grab(in)
val add = foo.record.value()
val result: Future[String] = dao.saveAndGetRecord(add.myobject)//saves and returns id as string
//the naive approach
val record = Await(result, Duration.inf)
push(out, Bar(record))// ***tests pass every time
//mapping the future approach
result.map { x=>
push(out, Bar(x))
} //***tests fail every time
下一個階段取決於從查詢返回的db記錄的id,但我想避免Await
。 我不確定為什么映射方法失敗:
"it should work" in {
val source = Source.single(Foo(Obj("hello", 1, "world")))
val probe = source
.via(new SaveAndGetId))
.runWith(TestSink.probe)
probe
.request(1)
.expectBarwithId("one")//say we know this will be
.expectComplete()
}
private implicit class RichTestProbe(probe: Probe[Bar]) {
def expectBarwithId(expected: String): Probe[Bar] =
probe.expectNextChainingPF{
case r @ Bar(str) if str == expected => r
}
}
當運行映射未來時,我會失敗:
should work ***FAILED***
java.lang.AssertionError: assertion failed: expected: message matching partial function but got unexpected message OnComplete
at scala.Predef$.assert(Predef.scala:170)
at akka.testkit.TestKitBase$class.expectMsgPF(TestKit.scala:406)
at akka.testkit.TestKit.expectMsgPF(TestKit.scala:814)
at akka.stream.testkit.TestSubscriber$ManualProbe.expectEventPF(StreamTestKit.scala:570)
文檔中的異步側通道示例在舞台的構造函數中具有未來,而不是在舞台中構建未來,因此似乎不適用於我的情況。
我同意拉蒙的觀點。 在這種情況下,不需要構造新的FlowShape
並且它太復雜了。 這里使用mapAsync
方法非常方便:
以下是使用mapAsync
的代碼段:
import akka.stream.scaladsl.{Sink, Source}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object MapAsyncExample {
val numOfParallelism: Int = 10
def main(args: Array[String]): Unit = {
Source.repeat(5)
.mapAsync(parallelism)(x => asyncSquare(x))
.runWith(Sink.foreach(println)) previous stage
}
//This method returns a Future
//You can replace this part with your database operations
def asyncSquare(value: Int): Future[Int] = Future {
value * value
}
}
在上面的代碼片段中, Source.repeat(5)
是一個無限發出5
的虛擬源。 有一個示例函數asyncSquare
,它接受一個integer
並在Future
計算它的平方。 .mapAsync(parallelism)(x => asyncSquare(x))
行使用該函數並將Future
的輸出發送到下一個階段。 在這種snipet,下一階段是sink
其打印每一個項目。
parallelism
是可以並發運行的最大asyncSquare
調用數。
我認為你的GraphStage
不必要地過於復雜。 以下Flow
執行相同的操作,無需編寫自定義階段:
val dao = new DbDao
val parallelism = 10 //number of parallel db queries
val SaveAndGetId : Flow[Foo, Bar, _] =
Flow[Foo]
.map(foo => foo.record.value().myobject)
.mapAsync(parallelism)(rec => dao.saveAndGetRecord(rec))
.map(Bar.apply)
我通常會嘗試將GraphStage
視為最后的手段,幾乎總是通過使用akka-stream庫提供的方法獲得相同的Flow的慣用方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.