pekko/akka-docs/src/test/scala/docs/stream/TwitterStreamQuickstartDocSpec.scala

234 lines
6.6 KiB
Scala
Raw Normal View History

/*
* Copyright (C) 2014-2019 Lightbend Inc. <https://www.lightbend.com>
*/
package docs.stream
//#imports
import akka.{ Done, NotUsed }
import akka.actor.ActorSystem
import akka.stream.{ ClosedShape, OverflowStrategy }
import akka.stream.scaladsl._
2015-01-15 15:02:19 +01:00
import scala.concurrent.Await
import scala.concurrent.Future
Various scala-2.13.0-M5 fixes fix akka-actor-tests compile errors some tests still fail though Fix test failures in akka-actor-test Manually work arround missing implicit Factory[Nothing, Seq[Nothing]] see https://github.com/scala/scala-collection-compat/issues/137 akka-remote scalafix changes Fix shutdownAll compile error test:akka-remote scalafix changes akka-multi-node-testkit scalafix Fix akka-remote-tests multi-jvm compile errors akka-stream-tests/test:scalafix Fix test:akka-stream-tests Crude implementation of ByteString.map scalafix akka-actor-typed, akka-actor-typed-tests akka-actor-typed-tests compile and succeed scalafix akka-camel scalafix akka-cluster akka-cluster compile & test scalafix akka-cluster-metrics Fix akka-cluster-metrics scalafix akka-cluster-tools akka-cluster-tools compile and test scalafix akka-distributed-data akka-distributed-data fixes scalafix akka-persistence scalafix akka-cluster-sharding fix akka-cluster-sharding scalafix akka-contrib Fix akka-cluster-sharding-typed test scalafix akka-docs Use scala-stm 0.9 (released for M5) akka-docs Remove dependency on collections-compat Cherry-pick the relevant constructs to our own private utils Shorten 'scala.collections.immutable' by importing it Duplicate 'immutable' imports Use 'foreach' on futures Replace MapLike with regular Map Internal API markers Simplify ccompat by moving PackageShared into object Since we don't currently need to differentiate between 2.11 and Avoid relying on 'union' (and ++) being left-biased Fix akka-actor/doc by removing -Ywarn-unused Make more things more private Copyright headers Use 'unsorted' to go from SortedSet to Set Duplicate import Use onComplete rather than failed.foreach Clarify why we partly duplicate scala-collection-compat
2018-11-22 16:18:10 +01:00
import scala.io.StdIn.readLine
//#imports
import akka.testkit.AkkaSpec
object TwitterStreamQuickstartDocSpec {
2017-05-17 17:32:15 +01:00
//#fiddle_code
2017-05-22 14:40:15 +01:00
import akka.NotUsed
import akka.actor.ActorSystem
import akka.stream.scaladsl._
2017-05-17 17:32:15 +01:00
//#model
final case class Author(handle: String)
final case class Hashtag(name: String)
final case class Tweet(author: Author, timestamp: Long, body: String) {
2019-03-11 10:38:24 +01:00
def hashtags: Set[Hashtag] =
body
.split(" ")
.collect {
case t if t.startsWith("#") => Hashtag(t.replaceAll("[^#\\w]", ""))
}
.toSet
}
2015-01-15 15:02:19 +01:00
val akkaTag = Hashtag("#akka")
//#model
2017-05-17 17:32:15 +01:00
//#fiddle_code
abstract class TweetSourceDecl {
//#tweet-source
val tweets: Source[Tweet, NotUsed]
//#tweet-source
}
2017-05-17 17:32:15 +01:00
//#fiddle_code
val tweets: Source[Tweet, NotUsed] = Source(
2015-01-15 15:02:19 +01:00
Tweet(Author("rolandkuhn"), System.currentTimeMillis, "#akka rocks!") ::
2019-03-11 10:38:24 +01:00
Tweet(Author("patriknw"), System.currentTimeMillis, "#akka !") ::
Tweet(Author("bantonsson"), System.currentTimeMillis, "#akka !") ::
Tweet(Author("drewhk"), System.currentTimeMillis, "#akka !") ::
Tweet(Author("ktosopl"), System.currentTimeMillis, "#akka on the rocks!") ::
Tweet(Author("mmartynas"), System.currentTimeMillis, "wow #akka !") ::
Tweet(Author("akkateam"), System.currentTimeMillis, "#akka rocks!") ::
Tweet(Author("bananaman"), System.currentTimeMillis, "#bananas rock!") ::
Tweet(Author("appleman"), System.currentTimeMillis, "#apples rock!") ::
Tweet(Author("drama"), System.currentTimeMillis, "we compared #apples to #oranges!") ::
Nil)
2017-05-17 17:32:15 +01:00
//#fiddle_code
}
class TwitterStreamQuickstartDocSpec extends AkkaSpec {
import TwitterStreamQuickstartDocSpec._
implicit val executionContext = system.dispatcher
// Disable println
def println(s: Any): Unit = ()
trait Example1 {
2017-05-17 17:32:15 +01:00
//#fiddle_code
//#first-sample
//#system-setup
implicit val system = ActorSystem("reactive-tweets")
//#system-setup
//#first-sample
2017-05-17 17:32:15 +01:00
//#fiddle_code
}
"filter and map" in {
//#first-sample
//#authors-filter-map
val authors: Source[Author, NotUsed] =
2019-03-11 10:38:24 +01:00
tweets.filter(_.hashtags.contains(akkaTag)).map(_.author)
//#first-sample
//#authors-filter-map
trait Example3 {
//#authors-collect
val authors: Source[Author, NotUsed] =
tweets.collect { case t if t.hashtags.contains(akkaTag) => t.author }
//#authors-collect
}
//#first-sample
//#authors-foreachsink-println
authors.runWith(Sink.foreach(println))
//#authors-foreachsink-println
//#first-sample
//#authors-foreach-println
authors.runForeach(println)
//#authors-foreach-println
}
"mapConcat hashtags" in {
//#hashtags-mapConcat
val hashtags: Source[Hashtag, NotUsed] = tweets.mapConcat(_.hashtags.toList)
//#hashtags-mapConcat
}
2014-12-11 14:57:48 +01:00
trait HiddenDefinitions {
2016-04-12 20:24:08 +08:00
//#graph-dsl-broadcast
val writeAuthors: Sink[Author, NotUsed] = ???
val writeHashtags: Sink[Hashtag, NotUsed] = ???
2016-04-12 20:24:08 +08:00
//#graph-dsl-broadcast
2014-12-11 14:57:48 +01:00
}
2014-12-11 14:57:48 +01:00
"simple broadcast" in {
val writeAuthors: Sink[Author, Future[Done]] = Sink.ignore
val writeHashtags: Sink[Hashtag, Future[Done]] = Sink.ignore
// format: OFF
2016-04-12 20:24:08 +08:00
//#graph-dsl-broadcast
val g = RunnableGraph.fromGraph(GraphDSL.create() { implicit b =>
import GraphDSL.Implicits._
val bcast = b.add(Broadcast[Tweet](2))
tweets ~> bcast.in
bcast.out(0) ~> Flow[Tweet].map(_.author) ~> writeAuthors
bcast.out(1) ~> Flow[Tweet].mapConcat(_.hashtags.toList) ~> writeHashtags
ClosedShape
})
g.run()
2016-04-12 20:24:08 +08:00
//#graph-dsl-broadcast
// format: ON
}
2017-05-17 17:32:15 +01:00
"simple fiddle showcase" in {
//#fiddle_code
tweets
.filterNot(_.hashtags.contains(akkaTag)) // Remove all tweets containing #akka hashtag
.map(_.hashtags) // Get all sets of hashtags ...
.reduce(_ ++ _) // ... and reduce them to a single set, removing duplicates across all tweets
.mapConcat(identity) // Flatten the set of hashtags to a stream of hashtags
.map(_.name.toUpperCase) // Convert all hashtags to upper case
.runWith(Sink.foreach(println)) // Attach the Flow to a Sink that will finally print the hashtags
2017-05-17 17:32:15 +01:00
//#fiddle_code
.value
}
"slowProcessing" in {
def slowComputation(t: Tweet): Long = {
Thread.sleep(500) // act as if performing some heavy computation
42
}
//#tweets-slow-consumption-dropHead
2019-03-11 10:38:24 +01:00
tweets.buffer(10, OverflowStrategy.dropHead).map(slowComputation).runWith(Sink.ignore)
//#tweets-slow-consumption-dropHead
}
"backpressure by readline" in {
trait X {
import scala.concurrent.duration._
//#backpressure-by-readline
val completion: Future[Done] =
2019-03-11 10:38:24 +01:00
Source(1 to 10).map(i => { println(s"map => $i"); i }).runForeach { i =>
readLine(s"Element = $i; continue reading? [press enter]\n")
}
Await.ready(completion, 1.minute)
//#backpressure-by-readline
}
}
"count elements on finite stream" in {
//#tweets-fold-count
val count: Flow[Tweet, Int, NotUsed] = Flow[Tweet].map(_ => 1)
val sumSink: Sink[Int, Future[Int]] = Sink.fold[Int, Int](0)(_ + _)
val counterGraph: RunnableGraph[Future[Int]] =
2019-03-11 10:38:24 +01:00
tweets.via(count).toMat(sumSink)(Keep.right)
val sum: Future[Int] = counterGraph.run()
sum.foreach(c => println(s"Total tweets processed: $c"))
//#tweets-fold-count
new AnyRef {
//#tweets-fold-count-oneline
val sum: Future[Int] = tweets.map(t => 1).runWith(sumSink)
//#tweets-fold-count-oneline
}
}
"materialize multiple times" in {
val tweetsInMinuteFromNow = tweets // not really in second, just acting as if
//#tweets-runnable-flow-materialized-twice
val sumSink = Sink.fold[Int, Int](0)(_ + _)
2015-06-23 18:41:55 +02:00
val counterRunnableGraph: RunnableGraph[Future[Int]] =
2019-03-11 10:38:24 +01:00
tweetsInMinuteFromNow.filter(_.hashtags contains akkaTag).map(t => 1).toMat(sumSink)(Keep.right)
// materialize the stream once in the morning
2015-06-23 18:41:55 +02:00
val morningTweetsCount: Future[Int] = counterRunnableGraph.run()
// and once in the evening, reusing the flow
2015-06-23 18:41:55 +02:00
val eveningTweetsCount: Future[Int] = counterRunnableGraph.run()
//#tweets-runnable-flow-materialized-twice
2015-06-23 18:41:55 +02:00
val sum: Future[Int] = counterRunnableGraph.run()
2019-03-11 10:38:24 +01:00
sum.map { c =>
println(s"Total tweets processed: $c")
}
}
}