2014-12-20 17:16:08 +01:00
|
|
|
/**
|
|
|
|
|
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
|
|
|
|
|
*/
|
|
|
|
|
package docs.stream
|
|
|
|
|
|
|
|
|
|
import akka.actor.Cancellable
|
2015-10-21 22:45:39 +02:00
|
|
|
import akka.stream.{ ClosedShape, FlowShape }
|
2014-12-20 17:16:08 +01:00
|
|
|
import akka.stream.scaladsl._
|
|
|
|
|
import akka.stream.testkit.AkkaSpec
|
|
|
|
|
|
2015-02-26 11:33:29 +01:00
|
|
|
import scala.concurrent.{ Promise, Future }
|
2014-12-20 17:16:08 +01:00
|
|
|
|
|
|
|
|
class FlowDocSpec extends AkkaSpec {
|
|
|
|
|
|
|
|
|
|
implicit val ec = system.dispatcher
|
|
|
|
|
|
|
|
|
|
//#imports
|
2015-06-23 18:28:53 +02:00
|
|
|
import akka.stream.ActorMaterializer
|
2014-12-20 17:16:08 +01:00
|
|
|
//#imports
|
|
|
|
|
|
2015-06-23 18:28:53 +02:00
|
|
|
implicit val mat = ActorMaterializer()
|
2014-12-20 17:16:08 +01:00
|
|
|
|
|
|
|
|
"source is immutable" in {
|
|
|
|
|
//#source-immutable
|
|
|
|
|
val source = Source(1 to 10)
|
2014-12-22 16:18:26 +01:00
|
|
|
source.map(_ => 0) // has no effect on source, since it's immutable
|
2014-12-20 17:16:08 +01:00
|
|
|
source.runWith(Sink.fold(0)(_ + _)) // 55
|
|
|
|
|
|
2014-12-22 16:18:26 +01:00
|
|
|
val zeroes = source.map(_ => 0) // returns new Source[Int], with `map()` appended
|
2014-12-20 17:16:08 +01:00
|
|
|
zeroes.runWith(Sink.fold(0)(_ + _)) // 0
|
|
|
|
|
//#source-immutable
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"materialization in steps" in {
|
|
|
|
|
//#materialization-in-steps
|
|
|
|
|
val source = Source(1 to 10)
|
|
|
|
|
val sink = Sink.fold[Int, Int](0)(_ + _)
|
|
|
|
|
|
2015-06-23 18:41:55 +02:00
|
|
|
// connect the Source to the Sink, obtaining a RunnableGraph
|
|
|
|
|
val runnable: RunnableGraph[Future[Int]] = source.toMat(sink)(Keep.right)
|
2014-12-20 17:16:08 +01:00
|
|
|
|
2015-01-28 14:19:50 +01:00
|
|
|
// materialize the flow and get the value of the FoldSink
|
|
|
|
|
val sum: Future[Int] = runnable.run()
|
2014-12-20 17:16:08 +01:00
|
|
|
|
|
|
|
|
//#materialization-in-steps
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"materialization runWith" in {
|
|
|
|
|
//#materialization-runWith
|
|
|
|
|
val source = Source(1 to 10)
|
|
|
|
|
val sink = Sink.fold[Int, Int](0)(_ + _)
|
|
|
|
|
|
|
|
|
|
// materialize the flow, getting the Sinks materialized value
|
|
|
|
|
val sum: Future[Int] = source.runWith(sink)
|
|
|
|
|
//#materialization-runWith
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-26 11:33:29 +01:00
|
|
|
"materialization is unique" in {
|
2014-12-20 17:16:08 +01:00
|
|
|
//#stream-reuse
|
2015-06-23 18:41:55 +02:00
|
|
|
// connect the Source to the Sink, obtaining a RunnableGraph
|
2014-12-20 17:16:08 +01:00
|
|
|
val sink = Sink.fold[Int, Int](0)(_ + _)
|
2015-06-23 18:41:55 +02:00
|
|
|
val runnable: RunnableGraph[Future[Int]] =
|
2015-01-28 14:19:50 +01:00
|
|
|
Source(1 to 10).toMat(sink)(Keep.right)
|
2014-12-20 17:16:08 +01:00
|
|
|
|
|
|
|
|
// get the materialized value of the FoldSink
|
2015-01-28 14:19:50 +01:00
|
|
|
val sum1: Future[Int] = runnable.run()
|
|
|
|
|
val sum2: Future[Int] = runnable.run()
|
2014-12-20 17:16:08 +01:00
|
|
|
|
|
|
|
|
// sum1 and sum2 are different Futures!
|
|
|
|
|
//#stream-reuse
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"compound source cannot be used as key" in {
|
2015-01-28 14:19:50 +01:00
|
|
|
// FIXME #16902 This example is now turned around
|
|
|
|
|
// The WRONG case has been switched
|
2014-12-20 17:16:08 +01:00
|
|
|
//#compound-source-is-not-keyed-runWith
|
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
|
case object Tick
|
|
|
|
|
|
2015-11-04 11:43:11 +02:00
|
|
|
val timer = Source.tick(initialDelay = 1.second, interval = 1.seconds, tick = () => Tick)
|
2014-12-20 17:16:08 +01:00
|
|
|
|
|
|
|
|
val timerCancel: Cancellable = Sink.ignore.runWith(timer)
|
|
|
|
|
timerCancel.cancel()
|
|
|
|
|
|
2014-12-22 16:18:26 +01:00
|
|
|
val timerMap = timer.map(tick => "tick")
|
2015-01-28 14:19:50 +01:00
|
|
|
// materialize the flow and retrieve the timers Cancellable
|
|
|
|
|
val timerCancellable = Sink.ignore.runWith(timerMap)
|
|
|
|
|
timerCancellable.cancel()
|
2014-12-20 17:16:08 +01:00
|
|
|
//#compound-source-is-not-keyed-runWith
|
|
|
|
|
|
|
|
|
|
//#compound-source-is-not-keyed-run
|
2015-01-28 14:19:50 +01:00
|
|
|
val timerCancellable2 = timerMap.to(Sink.ignore).run()
|
|
|
|
|
timerCancellable2.cancel()
|
2014-12-20 17:16:08 +01:00
|
|
|
//#compound-source-is-not-keyed-run
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"creating sources, sinks" in {
|
|
|
|
|
//#source-sink
|
|
|
|
|
// Create a source from an Iterable
|
|
|
|
|
Source(List(1, 2, 3))
|
|
|
|
|
|
2015-03-25 23:52:51 +09:00
|
|
|
// Create a source from a Future
|
2014-12-20 17:16:08 +01:00
|
|
|
Source(Future.successful("Hello Streams!"))
|
|
|
|
|
|
|
|
|
|
// Create a source from a single element
|
|
|
|
|
Source.single("only one element")
|
|
|
|
|
|
|
|
|
|
// an empty source
|
|
|
|
|
Source.empty
|
|
|
|
|
|
|
|
|
|
// Sink that folds over the stream and returns a Future
|
2015-02-26 11:33:29 +01:00
|
|
|
// of the final result as its materialized value
|
2014-12-20 17:16:08 +01:00
|
|
|
Sink.fold[Int, Int](0)(_ + _)
|
|
|
|
|
|
2015-02-26 11:33:29 +01:00
|
|
|
// Sink that returns a Future as its materialized value,
|
2014-12-20 17:16:08 +01:00
|
|
|
// containing the first element of the stream
|
|
|
|
|
Sink.head
|
|
|
|
|
|
|
|
|
|
// A Sink that consumes a stream without doing anything with the elements
|
|
|
|
|
Sink.ignore
|
|
|
|
|
|
|
|
|
|
// A Sink that executes a side-effecting call for every element of the stream
|
|
|
|
|
Sink.foreach[String](println(_))
|
|
|
|
|
//#source-sink
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"various ways of connecting source, sink, flow" in {
|
|
|
|
|
//#flow-connecting
|
|
|
|
|
// Explicitly creating and wiring up a Source, Sink and Flow
|
|
|
|
|
Source(1 to 6).via(Flow[Int].map(_ * 2)).to(Sink.foreach(println(_)))
|
|
|
|
|
|
|
|
|
|
// Starting from a Source
|
|
|
|
|
val source = Source(1 to 6).map(_ * 2)
|
|
|
|
|
source.to(Sink.foreach(println(_)))
|
|
|
|
|
|
|
|
|
|
// Starting from a Sink
|
2015-01-28 14:19:50 +01:00
|
|
|
val sink: Sink[Int, Unit] = Flow[Int].map(_ * 2).to(Sink.foreach(println(_)))
|
2014-12-20 17:16:08 +01:00
|
|
|
Source(1 to 6).to(sink)
|
|
|
|
|
|
2015-10-05 23:20:52 +02:00
|
|
|
// Broadcast to a sink inline
|
|
|
|
|
val otherSink: Sink[Int, Unit] =
|
|
|
|
|
Flow[Int].alsoTo(Sink.foreach(println(_))).to(Sink.ignore)
|
|
|
|
|
Source(1 to 6).to(otherSink)
|
|
|
|
|
|
2014-12-20 17:16:08 +01:00
|
|
|
//#flow-connecting
|
|
|
|
|
}
|
2015-02-26 11:33:29 +01:00
|
|
|
|
|
|
|
|
"various ways of transforming materialized values" in {
|
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
|
|
2015-11-30 15:45:37 +01:00
|
|
|
val throttler = Flow.fromGraph(GraphDSL.create(Source.tick(1.second, 1.second, "test")) { implicit builder =>
|
2015-02-26 11:33:29 +01:00
|
|
|
tickSource =>
|
2015-11-30 15:45:37 +01:00
|
|
|
import GraphDSL.Implicits._
|
2015-02-26 11:33:29 +01:00
|
|
|
val zip = builder.add(ZipWith[String, Int, Int](Keep.right))
|
|
|
|
|
tickSource ~> zip.in0
|
2015-10-21 22:45:39 +02:00
|
|
|
FlowShape(zip.in1, zip.out)
|
|
|
|
|
})
|
2015-02-26 11:33:29 +01:00
|
|
|
|
|
|
|
|
//#flow-mat-combine
|
2015-10-21 22:45:39 +02:00
|
|
|
// An source that can be signalled explicitly from the outside
|
|
|
|
|
val source: Source[Int, Promise[Option[Int]]] = Source.maybe[Int]
|
2015-02-26 11:33:29 +01:00
|
|
|
|
|
|
|
|
// A flow that internally throttles elements to 1/second, and returns a Cancellable
|
|
|
|
|
// which can be used to shut down the stream
|
|
|
|
|
val flow: Flow[Int, Int, Cancellable] = throttler
|
|
|
|
|
|
|
|
|
|
// A sink that returns the first element of a stream in the returned Future
|
2015-03-05 12:21:17 +01:00
|
|
|
val sink: Sink[Int, Future[Int]] = Sink.head[Int]
|
2015-02-26 11:33:29 +01:00
|
|
|
|
|
|
|
|
// By default, the materialized value of the leftmost stage is preserved
|
2015-10-21 22:45:39 +02:00
|
|
|
val r1: RunnableGraph[Promise[Option[Int]]] = source.via(flow).to(sink)
|
2015-02-26 11:33:29 +01:00
|
|
|
|
|
|
|
|
// Simple selection of materialized values by using Keep.right
|
2015-06-23 18:41:55 +02:00
|
|
|
val r2: RunnableGraph[Cancellable] = source.viaMat(flow)(Keep.right).to(sink)
|
|
|
|
|
val r3: RunnableGraph[Future[Int]] = source.via(flow).toMat(sink)(Keep.right)
|
2015-02-26 11:33:29 +01:00
|
|
|
|
|
|
|
|
// Using runWith will always give the materialized values of the stages added
|
|
|
|
|
// by runWith() itself
|
|
|
|
|
val r4: Future[Int] = source.via(flow).runWith(sink)
|
2015-10-21 22:45:39 +02:00
|
|
|
val r5: Promise[Option[Int]] = flow.to(sink).runWith(source)
|
|
|
|
|
val r6: (Promise[Option[Int]], Future[Int]) = flow.runWith(source, sink)
|
2015-02-26 11:33:29 +01:00
|
|
|
|
|
|
|
|
// Using more complext combinations
|
2015-10-21 22:45:39 +02:00
|
|
|
val r7: RunnableGraph[(Promise[Option[Int]], Cancellable)] =
|
2015-02-26 11:33:29 +01:00
|
|
|
source.viaMat(flow)(Keep.both).to(sink)
|
|
|
|
|
|
2015-10-21 22:45:39 +02:00
|
|
|
val r8: RunnableGraph[(Promise[Option[Int]], Future[Int])] =
|
2015-02-26 11:33:29 +01:00
|
|
|
source.via(flow).toMat(sink)(Keep.both)
|
|
|
|
|
|
2015-10-21 22:45:39 +02:00
|
|
|
val r9: RunnableGraph[((Promise[Option[Int]], Cancellable), Future[Int])] =
|
2015-02-26 11:33:29 +01:00
|
|
|
source.viaMat(flow)(Keep.both).toMat(sink)(Keep.both)
|
|
|
|
|
|
2015-06-23 18:41:55 +02:00
|
|
|
val r10: RunnableGraph[(Cancellable, Future[Int])] =
|
2015-02-26 11:33:29 +01:00
|
|
|
source.viaMat(flow)(Keep.right).toMat(sink)(Keep.both)
|
|
|
|
|
|
|
|
|
|
// It is also possible to map over the materialized values. In r9 we had a
|
|
|
|
|
// doubly nested pair, but we want to flatten it out
|
2015-10-21 22:45:39 +02:00
|
|
|
val r11: RunnableGraph[(Promise[Option[Int]], Cancellable, Future[Int])] =
|
2015-05-05 10:29:41 +02:00
|
|
|
r9.mapMaterializedValue {
|
2015-02-26 11:33:29 +01:00
|
|
|
case ((promise, cancellable), future) =>
|
|
|
|
|
(promise, cancellable, future)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now we can use pattern matching to get the resulting materialized values
|
|
|
|
|
val (promise, cancellable, future) = r11.run()
|
|
|
|
|
|
|
|
|
|
// Type inference works as expected
|
2015-10-21 22:45:39 +02:00
|
|
|
promise.success(None)
|
2015-02-26 11:33:29 +01:00
|
|
|
cancellable.cancel()
|
|
|
|
|
future.map(_ + 3)
|
|
|
|
|
|
|
|
|
|
// The result of r11 can be also achieved by using the Graph API
|
2015-10-21 22:45:39 +02:00
|
|
|
val r12: RunnableGraph[(Promise[Option[Int]], Cancellable, Future[Int])] =
|
2015-11-30 15:45:37 +01:00
|
|
|
RunnableGraph.fromGraph(GraphDSL.create(source, flow, sink)((_, _, _)) { implicit builder =>
|
2015-02-26 11:33:29 +01:00
|
|
|
(src, f, dst) =>
|
2015-11-30 15:45:37 +01:00
|
|
|
import GraphDSL.Implicits._
|
2015-02-26 11:33:29 +01:00
|
|
|
src ~> f ~> dst
|
2015-10-21 22:45:39 +02:00
|
|
|
ClosedShape
|
|
|
|
|
})
|
2015-02-26 11:33:29 +01:00
|
|
|
|
|
|
|
|
//#flow-mat-combine
|
|
|
|
|
}
|
2014-12-20 17:16:08 +01:00
|
|
|
}
|