├── actor-paths ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── src │ └── main │ │ └── scala │ │ ├── Counter.scala │ │ ├── Watcher.scala │ │ └── App.scala └── README.md ├── graph-flows ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── src │ └── main │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ └── GraphFlow.scala └── README.md ├── hello-akka ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── src │ └── main │ │ └── scala │ │ └── HelloAkkaScala.scala └── README.md ├── persistence ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── src │ └── main │ │ ├── resources │ │ └── application.conf │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ ├── Persistent.scala │ │ └── Counter.scala └── README.md ├── rest-api ├── project │ ├── plugins.sbt │ └── build.properties ├── src │ ├── main │ │ ├── scala │ │ │ └── org │ │ │ │ └── elu │ │ │ │ └── akka │ │ │ │ ├── db │ │ │ │ ├── DBResources.scala │ │ │ │ ├── MongoDB.scala │ │ │ │ └── TweetManager.scala │ │ │ │ ├── models │ │ │ │ ├── Tweet.scala │ │ │ │ └── TweetEntity.scala │ │ │ │ └── Api.scala │ │ └── resources │ │ │ └── application.conf │ └── test │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ └── ApiSpec.scala ├── README.md └── build.sbt ├── stream-io ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── src │ └── main │ │ ├── resources │ │ └── log.txt │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ ├── ReadStream.scala │ │ └── WriteStream.scala └── README.md ├── stream-test ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── src │ └── test │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ ├── StreamKitSpec.scala │ │ ├── SimpleStreamSpec.scala │ │ └── StreamActorSpec.scala └── README.md ├── test-fsm ├── project │ ├── plugins.sbt │ └── build.properties ├── README.md ├── build.sbt └── src │ ├── main │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ └── UserStorageFSM.scala │ └── test │ └── scala │ └── org │ └── elu │ └── akka │ └── UserStorageSpec.scala ├── akka-cluster ├── project │ ├── build.properties │ └── plugins.sbt ├── src │ └── main │ │ ├── scala │ │ └── org │ │ │ └── elu │ │ │ └── akka │ │ │ ├── commons │ │ │ └── Messages.scala │ │ │ ├── loadBalancing │ │ │ ├── LoadBalancingApp.scala │ │ │ ├── Backend.scala │ │ │ └── Frontend.scala │ │ │ ├── cluster │ │ │ ├── ClusterApp.scala │ │ │ ├── Frontend.scala │ │ │ └── Backend.scala │ │ │ ├── singleton │ │ │ ├── Frontend.scala │ │ │ ├── Worker.scala │ │ │ ├── Master.scala │ │ │ └── SingletonApp.scala │ │ │ └── sharding │ │ │ ├── Frontend.scala │ │ │ ├── Counter.scala │ │ │ └── ShardingApp.scala │ │ └── resources │ │ ├── sharding.conf │ │ ├── singleton.conf │ │ ├── application.conf │ │ └── loadbalancer.conf ├── build.sbt └── README.md ├── akka-remoting ├── project │ ├── build.properties │ └── plugins.sbt ├── build.sbt ├── README.md └── src │ └── main │ ├── scala │ └── org │ │ └── elu │ │ └── akka │ │ ├── Worker.scala │ │ └── Remote.scala │ └── resources │ └── application.conf ├── akka-routing ├── project │ ├── build.properties │ └── plugins.sbt ├── build.sbt ├── src │ └── main │ │ ├── resources │ │ └── application.conf │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ ├── Worker.scala │ │ ├── RouterApp.scala │ │ ├── Router.scala │ │ ├── RouterPool.scala │ │ ├── RoundRobin.scala │ │ └── Random.scala └── README.md ├── akka-streams ├── project │ ├── build.properties │ └── plugins.sbt ├── README.md ├── build.sbt └── src │ └── main │ └── scala │ └── org │ └── elu │ └── akka │ └── Stream.scala ├── hotswap-behavior ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── src │ └── main │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ ├── Become.scala │ │ └── UserStorageFSM.scala └── README.md ├── persistent-fsm ├── project │ ├── plugins.sbt │ └── build.properties ├── src │ └── main │ │ ├── resources │ │ └── application.conf │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ ├── PersistentFSM.scala │ │ ├── Reporter.scala │ │ └── Account.scala ├── build.sbt └── README.md ├── reactive-tweets ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── src │ └── main │ │ ├── resources │ │ └── application.conf │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ ├── Tweet.scala │ │ ├── ReactiveTweets.scala │ │ └── TwitterClient.scala └── README.md ├── shutdown-pattern ├── project │ ├── plugins.sbt │ └── build.properties ├── README.md ├── build.sbt └── src │ └── main │ └── scala │ └── org │ └── elu │ └── akka │ ├── ShutdownApp.scala │ └── pattern │ └── Reaper.scala ├── testing-actors ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── src │ ├── main │ │ └── scala │ │ │ └── org │ │ │ └── elu │ │ │ └── akka │ │ │ └── Counter.scala │ └── test │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ └── CounterSpec.scala └── README.md ├── balancing-workload ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── README.md └── src │ ├── main │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ ├── Worker.scala │ │ └── Master.scala │ └── test │ └── scala │ └── org │ └── elu │ └── akka │ └── WorkerSpec.scala ├── ordered-termination ├── project │ ├── plugins.sbt │ └── build.properties ├── README.md ├── build.sbt └── src │ └── main │ └── scala │ └── org │ └── elu │ └── akka │ ├── pattern │ ├── Worker.scala │ ├── Master.scala │ └── Terminator.scala │ └── OrderTermination.scala ├── playing-with-actors ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── src │ └── main │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ ├── Monitoring.scala │ │ ├── ActorCreation.scala │ │ ├── Supervision.scala │ │ └── TalkToActor.scala └── README.md ├── scheduler-messages ├── project │ ├── plugins.sbt │ └── build.properties ├── README.md ├── build.sbt └── src │ └── main │ └── scala │ └── org │ └── elu │ └── akka │ ├── SchedulingMessages.scala │ └── Scheduler.scala ├── testing-parent-child ├── project │ ├── plugins.sbt │ └── build.properties ├── src │ ├── main │ │ └── scala │ │ │ └── org │ │ │ └── elu │ │ │ └── akka │ │ │ ├── Child.scala │ │ │ └── Parent.scala │ └── test │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ ├── ChildSpec.scala │ │ └── ParentSpec.scala ├── build.sbt └── README.md ├── throttler-messages ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── src │ └── main │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ ├── ThrottlerApp.scala │ │ └── throttle │ │ └── TimerBasedThrottler.scala └── README.md ├── akka-http-client-side-api ├── project │ ├── plugins.sbt │ └── build.properties ├── src │ └── main │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ ├── Commons.scala │ │ ├── RequestLevel.scala │ │ ├── ConnectionLevel.scala │ │ └── HostLevel.scala ├── build.sbt └── README.md ├── akka-http-server-side-api ├── project │ ├── plugins.sbt │ └── build.properties ├── build.sbt ├── README.md └── src │ └── main │ └── scala │ └── org │ └── elu │ └── akka │ ├── HighLevel.scala │ └── LowLevel.scala ├── multi-node-testing ├── project │ ├── build.properties │ └── plugins.sbt ├── src │ ├── multi-jvm │ │ └── scala │ │ │ └── org │ │ │ └── elu │ │ │ └── akka │ │ │ ├── MultiNodeSampleConfig.scala │ │ │ ├── BasicMultiNodeSpec.scala │ │ │ └── MultiNodeSample.scala │ └── main │ │ └── scala │ │ └── org │ │ └── elu │ │ └── akka │ │ └── Worker.scala ├── README.md └── build.sbt ├── .gitignore ├── testing.md ├── akka-cluster.md ├── patterns.md ├── akka-basic-tools.md ├── persistence.md ├── README.md ├── http.md └── streams.md /actor-paths/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /graph-flows/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /hello-akka/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /persistence/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /rest-api/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /stream-io/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /stream-test/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /test-fsm/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /actor-paths/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /akka-cluster/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /akka-cluster/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /akka-remoting/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /akka-remoting/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /akka-routing/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.7 -------------------------------------------------------------------------------- /akka-routing/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /akka-streams/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /akka-streams/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /graph-flows/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /hello-akka/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /hotswap-behavior/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /persistence/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /persistent-fsm/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /reactive-tweets/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /rest-api/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.11 -------------------------------------------------------------------------------- /shutdown-pattern/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /stream-io/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /stream-test/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.11 -------------------------------------------------------------------------------- /test-fsm/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.11 -------------------------------------------------------------------------------- /testing-actors/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /balancing-workload/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /hotswap-behavior/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /ordered-termination/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /persistent-fsm/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /playing-with-actors/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /reactive-tweets/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /scheduler-messages/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /shutdown-pattern/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.11 -------------------------------------------------------------------------------- /testing-actors/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.11 -------------------------------------------------------------------------------- /testing-parent-child/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /throttler-messages/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /akka-http-client-side-api/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /akka-http-server-side-api/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /balancing-workload/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.11 -------------------------------------------------------------------------------- /multi-node-testing/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.11 -------------------------------------------------------------------------------- /ordered-termination/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.11 -------------------------------------------------------------------------------- /playing-with-actors/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /scheduler-messages/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.11 -------------------------------------------------------------------------------- /testing-parent-child/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.11 -------------------------------------------------------------------------------- /throttler-messages/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.11 -------------------------------------------------------------------------------- /akka-http-client-side-api/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /akka-http-server-side-api/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | lib_managed/ 3 | src_managed/ 4 | project/boot/ 5 | .history 6 | .cache -------------------------------------------------------------------------------- /multi-node-testing/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.11") -------------------------------------------------------------------------------- /test-fsm/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 6 : Testing Actors 2 | ## Testing FSM 3 | Simple example of how to test Actors with Finite State Machine (FSM). -------------------------------------------------------------------------------- /akka-streams/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 7: Working with Akka Streams 2 | ## Introduction to Akka Streams 3 | Very simple example of how to use Akka Streams. -------------------------------------------------------------------------------- /rest-api/src/main/scala/org/elu/akka/db/DBResources.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.db 2 | 3 | case class Created(id: String) 4 | 5 | case object Deleted -------------------------------------------------------------------------------- /rest-api/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 8: Working with Akka HTTP 2 | ## Let's Implement a REST API 3 | This is simple example on how to implement RESTful services with Akka HTTP. -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/commons/Messages.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.commons 2 | 3 | case class Add(num1: Int, num2: Int) 4 | case object BackendRegistration 5 | -------------------------------------------------------------------------------- /scheduler-messages/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 9: Working with Common Patterns in Akka 2 | ## Scheduling Periodic Messages 3 | In this example shown how to schedule periodic messages. -------------------------------------------------------------------------------- /rest-api/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | mongodb { 2 | database = "tweets" 3 | servers = ["localhost:27017"] 4 | } 5 | 6 | http { 7 | host = "0.0.0.0" 8 | port = 8000 9 | } -------------------------------------------------------------------------------- /actor-paths/build.sbt: -------------------------------------------------------------------------------- 1 | name := "actor-paths" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.3" 9 | ) -------------------------------------------------------------------------------- /hello-akka/build.sbt: -------------------------------------------------------------------------------- 1 | name := "hello-akka" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.0" 9 | ) -------------------------------------------------------------------------------- /hotswap-behavior/build.sbt: -------------------------------------------------------------------------------- 1 | name := "hotswap-behavior" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.3" 9 | ) -------------------------------------------------------------------------------- /akka-routing/build.sbt: -------------------------------------------------------------------------------- 1 | name := "akka-routing" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.7" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.3" 9 | ) 10 | -------------------------------------------------------------------------------- /playing-with-actors/build.sbt: -------------------------------------------------------------------------------- 1 | name := "playing-with-actors" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.3" 9 | ) -------------------------------------------------------------------------------- /ordered-termination/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 9: Working with Common Patterns in Akka 2 | ## Ordered Termination 3 | Example of actor's ordered termination with [An Akka 2 Terminator](http://letitcrash.com/post/29773618510/an-akka-2-terminator). -------------------------------------------------------------------------------- /shutdown-pattern/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 9: Working with Common Patterns in Akka 2 | ## Shutdown Patterns 3 | In this example explained how to use [Shutdown Patterns in Akka 2](http://letitcrash.com/post/30165507578/shutdown-patterns-in-akka-2). -------------------------------------------------------------------------------- /stream-io/build.sbt: -------------------------------------------------------------------------------- 1 | name := "stream-io" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.4", 9 | "com.typesafe.akka" %% "akka-stream" % "2.4.4" 10 | ) -------------------------------------------------------------------------------- /graph-flows/build.sbt: -------------------------------------------------------------------------------- 1 | name := "graph-flows" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.4", 9 | "com.typesafe.akka" %% "akka-stream" % "2.4.4" 10 | ) -------------------------------------------------------------------------------- /akka-streams/build.sbt: -------------------------------------------------------------------------------- 1 | name := "akka-streams" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.4", 9 | "com.typesafe.akka" %% "akka-stream" % "2.4.4" 10 | ) -------------------------------------------------------------------------------- /akka-remoting/build.sbt: -------------------------------------------------------------------------------- 1 | name := "akka-remoting" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.3", 9 | "com.typesafe.akka" %% "akka-remote" % "2.4.3" 10 | ) 11 | -------------------------------------------------------------------------------- /akka-http-client-side-api/src/main/scala/org/elu/akka/Commons.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import spray.json.DefaultJsonProtocol 4 | 5 | case class IpInfo(ip: String) 6 | 7 | object JsonProtocol extends DefaultJsonProtocol { 8 | implicit val format = jsonFormat1(IpInfo.apply) 9 | } -------------------------------------------------------------------------------- /akka-remoting/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 5 : Working with Akka Cluster 2 | ## Playing with Remote Actors 3 | Experimenting with Akka Remote package. Good explanation on the video about required dependencies and configurations in `application.conf`. Need to open 3 terminals to run different main classes on each. -------------------------------------------------------------------------------- /testing.md: -------------------------------------------------------------------------------- 1 | # Chapter 6 : Testing Actors 2 | In this chapter reviewing different ways to test Akka Actors. 3 | ## Contents 4 | [How to Test an Actor?](testing-actors) 5 | [Testing a Parent-child Relationship](testing-parent-child) 6 | [Testing FSM](test-fsm) 7 | [Multi Node Testing](multi-node-testing) -------------------------------------------------------------------------------- /akka-routing/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka.actor.deployment { 2 | /random-router-pool { 3 | router = random-pool 4 | nr-of-instances = 3 5 | } 6 | /round-robin-group { 7 | router = round-robin-group 8 | routees.paths = ["/user/w1", "/user/w2", "/user/w3"] 9 | } 10 | } -------------------------------------------------------------------------------- /testing-parent-child/src/main/scala/org/elu/akka/Child.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{ActorRef, Actor} 4 | 5 | /** Created by luhtonen on 18/04/16. */ 6 | class Child(parent: ActorRef) extends Actor { 7 | def receive = { 8 | case "ping" => parent ! "pong" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /rest-api/src/main/scala/org/elu/akka/models/Tweet.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.models 2 | 3 | import spray.json.DefaultJsonProtocol 4 | 5 | case class Tweet(author: String, body: String) 6 | 7 | object TweetProtocol extends DefaultJsonProtocol { 8 | implicit val format = jsonFormat2(Tweet.apply) 9 | } 10 | -------------------------------------------------------------------------------- /reactive-tweets/build.sbt: -------------------------------------------------------------------------------- 1 | name := "reactive-tweets" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.4", 9 | "com.typesafe.akka" %% "akka-stream" % "2.4.4", 10 | "org.twitter4j" % "twitter4j-stream" % "4.0.4" 11 | ) -------------------------------------------------------------------------------- /reactive-tweets/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | Twitter { 2 | apiKey = "8pzQ80h9bJJGmNQWSXPU8VhGe" 3 | apiSecret = "vZW53ln7EDvU3OshogHhjGcJeQuVULlEYZPxuobxbqBEoQ6GPk" 4 | accessToken = "306398721-tkG06J06w5lyENffp1xFv4eI59mVbIhpeoGsHMH0" 5 | accessTokenSecret = "s4w88zPoBBaQAxYHckSvgq5azQud3lOhuw7HO3jU3KMdS" 6 | } -------------------------------------------------------------------------------- /test-fsm/build.sbt: -------------------------------------------------------------------------------- 1 | name := "test-fsm" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.3", 9 | "org.scalatest" %% "scalatest" % "2.2.6" % "test", 10 | "com.typesafe.akka" %% "akka-testkit" % "2.4.3" % "test" 11 | ) 12 | -------------------------------------------------------------------------------- /testing-actors/build.sbt: -------------------------------------------------------------------------------- 1 | name := "testing-actors" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.3", 9 | "org.scalatest" %% "scalatest" % "2.2.6" % "test", 10 | "com.typesafe.akka" %% "akka-testkit" % "2.4.3" % "test" 11 | ) -------------------------------------------------------------------------------- /testing-parent-child/build.sbt: -------------------------------------------------------------------------------- 1 | name := "testing-parent-child" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.3", 9 | "org.scalatest" %% "scalatest" % "2.2.6" % "test", 10 | "com.typesafe.akka" %% "akka-testkit" % "2.4.3" % "test" 11 | ) -------------------------------------------------------------------------------- /multi-node-testing/src/multi-jvm/scala/org/elu/akka/MultiNodeSampleConfig.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.remote.testkit.MultiNodeConfig 4 | 5 | 6 | /** Created by luhtonen on 18/04/16. */ 7 | object MultiNodeSampleConfig extends MultiNodeConfig { 8 | val node1 = role("node1") 9 | val node2 = role("node2") 10 | } 11 | -------------------------------------------------------------------------------- /shutdown-pattern/build.sbt: -------------------------------------------------------------------------------- 1 | name := "shutdown-pattern" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | val akkaVersion = "2.4.4" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion, 12 | "org.scalatest" %% "scalatest" % "2.2.6" % "test" 13 | ) -------------------------------------------------------------------------------- /balancing-workload/build.sbt: -------------------------------------------------------------------------------- 1 | name := "balancing-workload" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | val akkaVersion = "2.4.4" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion, 12 | "org.scalatest" %% "scalatest" % "2.2.6" % "test" 13 | ) -------------------------------------------------------------------------------- /persistence/build.sbt: -------------------------------------------------------------------------------- 1 | name := "persistence" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.3", 9 | "com.typesafe.akka" %% "akka-persistence" % "2.4.3", 10 | "org.iq80.leveldb" % "leveldb" % "0.7", 11 | "org.fusesource.leveldbjni" % "leveldbjni-all" % "1.8" 12 | ) 13 | -------------------------------------------------------------------------------- /scheduler-messages/build.sbt: -------------------------------------------------------------------------------- 1 | name := "scheduler-messages" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | val akkaVersion = "2.4.4" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion, 12 | "org.scalatest" %% "scalatest" % "2.2.6" % "test" 13 | ) -------------------------------------------------------------------------------- /throttler-messages/build.sbt: -------------------------------------------------------------------------------- 1 | name := "throttler-messages" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | val akkaVersion = "2.4.4" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion, 12 | "org.scalatest" %% "scalatest" % "2.2.6" % "test" 13 | ) -------------------------------------------------------------------------------- /ordered-termination/build.sbt: -------------------------------------------------------------------------------- 1 | name := "ordered-termination" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | val akkaVersion = "2.4.4" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion, 12 | "org.scalatest" %% "scalatest" % "2.2.6" % "test" 13 | ) 14 | -------------------------------------------------------------------------------- /testing-parent-child/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 6 : Testing Actors 2 | ## Testing a Parent-child Relationship 3 | This is simple example on how to test parent-child relationship. To make actors testable implementation should be changed to use dependency injection. 4 | 5 | ### Note about sbt version 6 | In order to use `scalatest` version `2.2.6` version of `sbt` should be set to `0.13.11`. -------------------------------------------------------------------------------- /akka-cluster.md: -------------------------------------------------------------------------------- 1 | # Chapter 5 : Working with Akka Cluster 2 | In this chapter experiments with remote actores and Akka Cluster 3 | 4 | ## Contents 5 | [Playing with Remote Actors](akka-remoting) 6 | [Building a Cluster](akka-cluster) 7 | [Adding Load Balancer to a Cluster Node](akka-cluster) 8 | [Creating a Singleton Actor in the Cluster](akka-cluster) 9 | [Cluster Sharding](akka-cluster) -------------------------------------------------------------------------------- /patterns.md: -------------------------------------------------------------------------------- 1 | # Chapter 9: Working with Common Patterns in Akka 2 | Examples of how to use different common design patterns with Akka. 3 | 4 | ## Contents 5 | [Balancing Workload Across Nodes](balancing-workload) 6 | [Throttling Messages](throttler-messages) 7 | [Shutdown Patterns](shutdown-pattern) 8 | [Ordered Termination](ordered-termination) 9 | [Scheduling Periodic Messages](scheduler-messages) -------------------------------------------------------------------------------- /stream-io/src/main/resources/log.txt: -------------------------------------------------------------------------------- 1 | 1. [INFO] starting system 2 | 2. [DEBUG] starting subsystem 1 3 | 3. [ERROR] could not initalize subsystem 1 4 | 4. [DEBUG] starting subsystem 2 5 | 5. [ERROR] could not initalize subsystem 2 6 | 6. something else 7 | 7. [WARN] bad sign 8 | 8. [INFO] aright, sorted it out 9 | 9. [DEBUG] working... 10 | 10. [DEBUG] all done 11 | 11. [INFO] shutting down system -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/loadBalancing/LoadBalancingApp.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.loadBalancing 2 | 3 | /** Created by luhtonen on 13/04/16. */ 4 | object LoadBalancingApp extends App { 5 | 6 | //initiate three nodes from backend 7 | Backend.initiate(2551) 8 | 9 | Backend.initiate(2552) 10 | 11 | Backend.initiate(2561) 12 | 13 | //initiate frontend node 14 | Frontend.initiate() 15 | } 16 | -------------------------------------------------------------------------------- /akka-remoting/src/main/scala/org/elu/akka/Worker.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.Actor 4 | 5 | /** Created by luhtonen on 12/04/16. */ 6 | class Worker extends Actor { 7 | import Worker._ 8 | 9 | def receive = { 10 | case msg: Work => 11 | println(s"I received Work Message and My ActorRef: ${self}") 12 | } 13 | } 14 | 15 | object Worker { 16 | case class Work(message: String) 17 | } -------------------------------------------------------------------------------- /akka-routing/src/main/scala/org/elu/akka/Worker.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.Actor 4 | import org.elu.akka.Worker.Work 5 | 6 | /** Created by luhtonen on 12/04/16. */ 7 | class Worker extends Actor { 8 | 9 | def receive = { 10 | case msg: Work => 11 | println(s"I received Work Message and My ActorRef: ${self}") 12 | } 13 | } 14 | 15 | object Worker { 16 | case class Work() 17 | } -------------------------------------------------------------------------------- /reactive-tweets/src/main/scala/org/elu/akka/Tweet.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | case class Author(name: String) 4 | 5 | case class Hashtag(name: String){ 6 | require(name.startsWith("#"), "Hash tag must start with #") 7 | } 8 | 9 | case class Tweet(author: Author, body: String) { 10 | def hashtags: Set[Hashtag] = { 11 | body.split(" ").collect{ case t if t.startsWith("#") => Hashtag(t)}.toSet 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /stream-test/build.sbt: -------------------------------------------------------------------------------- 1 | name := "stream-test" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.4", 9 | "com.typesafe.akka" %% "akka-stream" % "2.4.4", 10 | "com.typesafe.akka" %% "akka-stream-testkit" % "2.4.4", 11 | "org.scalatest" %% "scalatest" % "2.2.6" % "test", 12 | "com.typesafe.akka" %% "akka-testkit" % "2.4.4" % "test" 13 | ) -------------------------------------------------------------------------------- /actor-paths/src/main/scala/Counter.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.Actor 4 | import org.elu.akka.Counter.{Dec, Inc} 5 | 6 | object Counter { 7 | final case class Inc(num: Int) 8 | final case class Dec(num: Int) 9 | } 10 | 11 | class Counter extends Actor { 12 | 13 | var count = 0 14 | 15 | def receive = { 16 | case Inc(x) => 17 | count += x 18 | case Dec(x) => 19 | count -= x 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /persistence/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | persistence { 3 | journal { 4 | plugin = "akka.persistence.journal.leveldb", 5 | leveldb { 6 | dir = "target/example/journal", 7 | native = false 8 | } 9 | }, 10 | snapshot-store { 11 | plugin = "akka.persistence.snapshot-store.local", 12 | local { 13 | dir = "target/example/snapshots" 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /persistent-fsm/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | persistence { 3 | journal { 4 | plugin = "akka.persistence.journal.leveldb", 5 | leveldb { 6 | dir = "target/example/journal", 7 | native = false 8 | } 9 | }, 10 | snapshot-store { 11 | plugin = "akka.persistence.snapshot-store.local", 12 | local { 13 | dir = "target/example/snapshots" 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /testing-parent-child/src/main/scala/org/elu/akka/Parent.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Actor, ActorRef, ActorRefFactory} 4 | 5 | /** Created by luhtonen on 18/04/16. */ 6 | class Parent(childMaker: ActorRefFactory => ActorRef) extends Actor { 7 | val child = childMaker(context) 8 | var ponged = false 9 | 10 | def receive = { 11 | case "ping" => child ! "ping" 12 | case "pong" => ponged = true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /multi-node-testing/src/main/scala/org/elu/akka/Worker.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.Actor 4 | 5 | /** Created by luhtonen on 18/04/16. */ 6 | class Worker extends Actor { 7 | import Worker._ 8 | 9 | def receive = { 10 | case Work => 11 | println(s"I received Work Message and My ActorRef: ${self}") 12 | sender() ! Done 13 | } 14 | } 15 | 16 | object Worker { 17 | case object Work 18 | case object Done 19 | } -------------------------------------------------------------------------------- /akka-http-client-side-api/build.sbt: -------------------------------------------------------------------------------- 1 | name := "akka-http-client-side-api" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.4", 9 | "com.typesafe.akka" %% "akka-stream" % "2.4.4", 10 | "com.typesafe.akka" %% "akka-http-core" % "2.4.4", 11 | "com.typesafe.akka" %% "akka-http-experimental" % "2.4.4", 12 | "com.typesafe.akka" %% "akka-http-spray-json-experimental" % "2.4.4" 13 | ) -------------------------------------------------------------------------------- /akka-http-server-side-api/build.sbt: -------------------------------------------------------------------------------- 1 | name := "akka-http-server-side-api" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.4", 9 | "com.typesafe.akka" %% "akka-stream" % "2.4.4", 10 | "com.typesafe.akka" %% "akka-http-core" % "2.4.4", 11 | "com.typesafe.akka" %% "akka-http-experimental" % "2.4.4", 12 | "com.typesafe.akka" %% "akka-http-spray-json-experimental" % "2.4.4" 13 | ) -------------------------------------------------------------------------------- /akka-basic-tools.md: -------------------------------------------------------------------------------- 1 | # Chapter 3: Working with Akka Basic Tools 2 | In this chapter are introduced Akka's basic tools like `ActorRef`, actor's path and selection, message routing and actor behavior replacement. 3 | 4 | ## Contents 5 | [ActorRef Versus Actor Path Versus Actor Selection](actor-paths) 6 | [Sending Messages via Router](akka-routing) 7 | [Replacing Actor Behavior via become/unbecome](hotswap-behavior) 8 | [Replacing Actor Behavior via FSM](hotswap-behavior#replacing-actor-behavior-via-fsm) -------------------------------------------------------------------------------- /persistent-fsm/src/main/scala/org/elu/akka/PersistentFSM.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{ActorSystem, Props} 4 | 5 | /** Created by luhtonen on 12/04/16. */ 6 | object PersistentFSMApp extends App { 7 | import Account._ 8 | 9 | val system = ActorSystem("persistent-fsm-actors") 10 | val account = system.actorOf(Props[Account]) 11 | 12 | account ! Operation(1000, CR) 13 | account ! Operation(10, DR) 14 | 15 | Thread.sleep(1000) 16 | system.terminate() 17 | } 18 | -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/cluster/ClusterApp.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.cluster 2 | 3 | import org.elu.akka.commons.Add 4 | 5 | /** Created by luhtonen on 13/04/16. */ 6 | object ClusterApp extends App { 7 | 8 | //initiate frontend node 9 | Frontend.initiate() 10 | 11 | //initiate three nodes from backend 12 | Backend.initiate(2552) 13 | Backend.initiate(2560) 14 | Backend.initiate(2561) 15 | 16 | Thread.sleep(10000) 17 | 18 | Frontend.getFrontend ! Add(2, 4) 19 | } 20 | -------------------------------------------------------------------------------- /ordered-termination/src/main/scala/org/elu/akka/pattern/Worker.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.pattern 2 | 3 | import akka.actor.Actor 4 | 5 | /** Created by luhtonen on 28/04/16. */ 6 | class Worker extends Actor { 7 | override def preStart() { 8 | println("%s is running".format(self.path.name)) 9 | } 10 | override def postStop() { 11 | println("%s has stopped".format(self.path.name)) 12 | } 13 | def receive = { 14 | case msg => 15 | println("Cool, I got a message: " + msg) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /persistent-fsm/build.sbt: -------------------------------------------------------------------------------- 1 | name := "persistent-fsm" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.3", 9 | "com.typesafe.akka" %% "akka-persistence" % "2.4.3", 10 | "org.iq80.leveldb" % "leveldb" % "0.7", 11 | "org.fusesource.leveldbjni" % "leveldbjni-all" % "1.8", 12 | "com.typesafe.akka" %% "akka-persistence-query-experimental" % "2.4.3", 13 | "com.typesafe.akka" % "akka-stream-experimental_2.11" % "2.0.4" 14 | ) -------------------------------------------------------------------------------- /persistence/src/main/scala/org/elu/akka/Persistent.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{ActorSystem, Props} 4 | 5 | /** Created by luhtonen on 12/04/16. */ 6 | object Persistent extends App { 7 | import Counter._ 8 | 9 | val system = ActorSystem("persistent-actors") 10 | val counter = system.actorOf(Props[Counter]) 11 | 12 | counter ! Cmd(Increment(3)) 13 | counter ! Cmd(Increment(5)) 14 | counter ! Cmd(Decrement(3)) 15 | counter ! "print" 16 | 17 | Thread.sleep(1000) 18 | system.terminate() 19 | } 20 | -------------------------------------------------------------------------------- /multi-node-testing/src/multi-jvm/scala/org/elu/akka/BasicMultiNodeSpec.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.remote.testkit.MultiNodeSpecCallbacks 4 | import org.scalatest.{FlatSpecLike, MustMatchers, BeforeAndAfterAll} 5 | 6 | /** Created by luhtonen on 18/04/16. */ 7 | trait BasicMultiNodeSpec extends MultiNodeSpecCallbacks 8 | with FlatSpecLike 9 | with MustMatchers 10 | with BeforeAndAfterAll { 11 | 12 | override def beforeAll = multiNodeSpecBeforeAll() 13 | 14 | override def afterAll = multiNodeSpecAfterAll() 15 | } 16 | -------------------------------------------------------------------------------- /scheduler-messages/src/main/scala/org/elu/akka/SchedulingMessages.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Props, ActorSystem} 4 | 5 | /** Created by luhtonen on 28/04/16. */ 6 | object SchedulingMessages extends App { 7 | 8 | val system = ActorSystem("Scheduling-Messages") 9 | 10 | // val scheduler = system.actorOf(Props[ScheduleInConstrutor], "schedule-in-constructor") 11 | 12 | val scheduler = system.actorOf(Props[ScheduleInReceive], "schedule-in-receive") 13 | 14 | Thread.sleep(5000) 15 | system.terminate() 16 | } 17 | -------------------------------------------------------------------------------- /testing-actors/src/main/scala/org/elu/akka/Counter.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.Actor 4 | 5 | /** Created by luhtonen on 17/04/16. */ 6 | class Counter extends Actor { 7 | import Counter._ 8 | 9 | var count: Int = 0 10 | 11 | def receive = { 12 | case Increment => 13 | count += 1 14 | case Decrement => 15 | count -= 1 16 | case GetCount => 17 | sender ! count 18 | } 19 | } 20 | 21 | object Counter { 22 | case object Increment 23 | case object Decrement 24 | case object GetCount 25 | } -------------------------------------------------------------------------------- /testing-actors/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 6 : Testing Actors 2 | ## How to Test an Actor? 3 | This is simple example of how to test Akka Actors with [ScalaTest](http://www.scalatest.org/) and [TestKit](http://doc.akka.io/docs/akka/current/scala/testing.html) test frameworks. 4 | 5 | ### Note on ScalaTest version 6 | I've used `ScalaTest` version `2.2.6`. This version was producing warning about conflicting packages with `sbt` version `0.13.8`. In order to resolve this issue I have to upgrade `sbt` to version `0.13.11` in `build.properties` file, which can be found under `project` directory. -------------------------------------------------------------------------------- /akka-http-client-side-api/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 8: Working with Akka HTTP 2 | ## Working with Client-side API 3 | In this examples shown how to create HTTP client to consume external services. 3 levels of client-side API is available in Akka HTTP: *connection*, *host* and *request*. **Connection level** is the lowest level API and provides more granular control of connection handling. **Host level** is build on top of *connection level* and provide control to host connections. **Request level** is build on top of *host level* and is simplest of all 3, but provides access only to request API. -------------------------------------------------------------------------------- /ordered-termination/src/main/scala/org/elu/akka/OrderTermination.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{PoisonPill, Props, ActorSystem} 4 | import org.elu.akka.pattern._ 5 | 6 | /** Created by luhtonen on 28/04/16. */ 7 | object OrderTermination extends App { 8 | 9 | val system = ActorSystem("order-termination") 10 | val terminator = system.actorOf(Props(new Terminator(Props[Worker], 5))) 11 | val master = system.actorOf(Props(new Master(terminator))) 12 | 13 | master ! "hello world" 14 | master ! PoisonPill 15 | 16 | Thread.sleep(5000) 17 | system.terminate() 18 | } 19 | -------------------------------------------------------------------------------- /persistence.md: -------------------------------------------------------------------------------- 1 | # Chapter 4: Akka Persistence 2 | Examples of Akka Persistence. 3 | 4 | ## Contents 5 | [Creating Persistent Actors](persistence) 6 | [Playing with a Persistent Actor](persistence) 7 | [Persistence FSM](persistent-fsm) 8 | [Persistence Query](persistent-fsm) 9 | 10 | ## TODO 11 | Following warning is shown: 12 | 13 | Using the default Java serializer for class [...] which is not recommended because of 14 | performance implications. Use another serializer or disable this warning using the 15 | setting 'akka.actor.warn-about-java-serializer-usage' 16 | 17 | It does not cause any problems but still will be nice to fix. -------------------------------------------------------------------------------- /akka-cluster/build.sbt: -------------------------------------------------------------------------------- 1 | name := "akka-cluster" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.3", 9 | "com.typesafe.akka" %% "akka-remote" % "2.4.3", 10 | "com.typesafe.akka" %% "akka-cluster" % "2.4.3", 11 | "com.typesafe.akka" %% "akka-cluster-tools" % "2.4.3", 12 | "com.typesafe.akka" %% "akka-cluster-sharding" % "2.4.3", 13 | "com.typesafe.akka" %% "akka-persistence" % "2.4.3", 14 | "com.typesafe.akka" %% "akka-contrib" % "2.4.3", 15 | "org.iq80.leveldb" % "leveldb" % "0.7", 16 | "org.fusesource.leveldbjni" % "leveldbjni-all" % "1.8" 17 | ) -------------------------------------------------------------------------------- /hello-akka/src/main/scala/HelloAkkaScala.scala: -------------------------------------------------------------------------------- 1 | import akka.actor.{Props, ActorSystem, Actor} 2 | 3 | // Define Actor Messages 4 | case class WhoToGreet(who: String) 5 | 6 | // Define Greeter Actor 7 | class Greeter extends Actor { 8 | def receive = { 9 | case WhoToGreet(who) => println(s"Hello $who") 10 | } 11 | } 12 | 13 | object HelloAkkaScala extends App { 14 | 15 | // Create the 'hello akka' actor system 16 | val system = ActorSystem("Hello-Akka") 17 | 18 | // Create the 'greeter' actor 19 | val greeter = system.actorOf(Props[Greeter], "greeter") 20 | 21 | // Send WhoToGreet Message to actor 22 | greeter ! WhoToGreet("Akka") 23 | } 24 | -------------------------------------------------------------------------------- /rest-api/src/main/scala/org/elu/akka/db/MongoDB.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.db 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import reactivemongo.api.MongoDriver 5 | 6 | import scala.collection.JavaConverters._ 7 | import scala.concurrent.ExecutionContext.Implicits.global 8 | 9 | /** Created by luhtonen on 22/04/16. */ 10 | object MongoDB { 11 | 12 | val config = ConfigFactory.load() 13 | val database = config.getString("mongodb.database") 14 | val servers = config.getStringList("mongodb.servers").asScala 15 | 16 | val driver = new MongoDriver 17 | val connection = driver.connection(servers) 18 | 19 | val db = connection.database(database) 20 | } 21 | -------------------------------------------------------------------------------- /rest-api/build.sbt: -------------------------------------------------------------------------------- 1 | name := "rest-api" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | val akkaVersion = "2.4.4" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.typesafe.akka" %% "akka-stream" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-http-core" % akkaVersion, 13 | "com.typesafe.akka" %% "akka-http-experimental" % akkaVersion, 14 | "com.typesafe.akka" %% "akka-http-spray-json-experimental" % akkaVersion, 15 | "org.reactivemongo" %% "reactivemongo" % "0.11.11", 16 | "org.scalatest" %% "scalatest" % "2.2.6" % "test", 17 | "com.typesafe.akka" %% "akka-http-testkit" % akkaVersion 18 | ) -------------------------------------------------------------------------------- /akka-http-server-side-api/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 8: Working with Akka HTTP 2 | ## Working with Server-side API 3 | In this sample shown how to create simple HTTP server with `Akka HTTP`. There are 2 levels of API for that: *low-level* and *high-level*. **Low-level** allows to create server with source, flow and sink on the HTTP connection level. **High-level** offers simple DSL to define routing handled by server. 4 | 5 | ### Note about used deprecated API 6 | In the original video example is used deprecated API: 7 | 8 | Console.readLine() 9 | 10 | It should be replaced with the following code (including import): 11 | 12 | import scala.io.StdIn 13 | 14 | StdIn.readLine() 15 | 16 | -------------------------------------------------------------------------------- /shutdown-pattern/src/main/scala/org/elu/akka/ShutdownApp.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import java.util.Date 4 | 5 | import akka.actor.{PoisonPill, Props, ActorSystem, Actor} 6 | import org.elu.akka.pattern.{Reaper, ReaperWatched} 7 | 8 | class Target extends Actor with ReaperWatched { 9 | def receive = { 10 | case msg => 11 | println(s"[${new Date().toString}]I received a message: $msg") 12 | } 13 | } 14 | 15 | object ShutdownApp extends App { 16 | val system = ActorSystem("shutdown") 17 | val reaper = system.actorOf(Props[Reaper], Reaper.name) 18 | val target = system.actorOf(Props[Target], "target") 19 | 20 | target ! "Hello World" 21 | target ! PoisonPill 22 | } 23 | -------------------------------------------------------------------------------- /actor-paths/src/main/scala/Watcher.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor._ 4 | 5 | class Watcher extends Actor { 6 | 7 | var counterRef: ActorRef = _ 8 | val selection = context.actorSelection("/user/counter") 9 | 10 | selection ! Identify(None) 11 | 12 | def receive = { 13 | case ActorIdentity(_, Some(ref)) => 14 | println(s"Actor Reference for counter is ${ref}") 15 | case ActorIdentity(_, None) => 16 | println("Actor selection for actor doesn't live :(") 17 | } 18 | } 19 | 20 | object Watch extends App { 21 | val system = ActorSystem("Watch-actor-selection") 22 | 23 | val counter = system.actorOf(Props[Counter], "counter") 24 | val watcher = system.actorOf(Props[Watcher], "watcher") 25 | 26 | Thread.sleep(1000) 27 | 28 | system.terminate() 29 | } 30 | -------------------------------------------------------------------------------- /actor-paths/src/main/scala/App.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{PoisonPill, Props, ActorSystem} 4 | 5 | object ActorPath extends App { 6 | 7 | val system = ActorSystem("Actor-Path") 8 | 9 | val counter1 = system.actorOf(Props[Counter], "Counter") 10 | println(s"Actor Reference for counter1: $counter1") 11 | 12 | val counterSelection1 = system.actorSelection("counter") 13 | println(s"Actor Selection for counter1: $counterSelection1") 14 | 15 | counter1 ! PoisonPill 16 | Thread.sleep(100) 17 | 18 | val counter2 = system.actorOf(Props[Counter], "counter") 19 | println(s"Actor Reference for counter1: $counter2") 20 | 21 | val counterSelection2 = system.actorSelection("counter") 22 | println(s"Actor Selection for counter2: $counterSelection2") 23 | 24 | system.terminate() 25 | } 26 | -------------------------------------------------------------------------------- /ordered-termination/src/main/scala/org/elu/akka/pattern/Master.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.pattern 2 | 3 | import akka.actor.{Actor, ActorRef} 4 | 5 | /** Created by luhtonen on 28/04/16. */ 6 | class Master(terminator: ActorRef) extends Actor { 7 | import Terminator._ 8 | 9 | // Ask for the kids 10 | override def preStart() { 11 | terminator ! GetChildren(self) 12 | } 13 | 14 | // Wait for the kids to show up 15 | def waiting: Receive = { 16 | case Children(kids) => 17 | // become our initialized state 18 | context.become(initialized(kids)) 19 | } 20 | 21 | // Do our normal business logic 22 | def initialized(kids: Iterable[ActorRef]): Receive = { 23 | case msg => 24 | println(s"Cool, I got a message: $msg") 25 | } 26 | 27 | // Start waiting 28 | def receive = waiting 29 | } 30 | -------------------------------------------------------------------------------- /testing-parent-child/src/test/scala/org/elu/akka/ChildSpec.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Props, ActorSystem} 4 | import akka.testkit.{TestProbe, ImplicitSender, TestKit} 5 | import org.scalatest.{MustMatchers, BeforeAndAfterAll, FlatSpecLike} 6 | 7 | /** Created by luhtonen on 18/04/16. */ 8 | class ChildSpec extends TestKit(ActorSystem("test-system")) 9 | with ImplicitSender 10 | with FlatSpecLike 11 | with BeforeAndAfterAll 12 | with MustMatchers { 13 | 14 | override def afterAll = { 15 | TestKit.shutdownActorSystem(system) 16 | } 17 | 18 | "Child Actor" should "send pong message when receive ping message" in { 19 | val parent = TestProbe() 20 | 21 | val child = system.actorOf(Props(new Child(parent.ref))) 22 | 23 | child ! "ping" 24 | 25 | parent.expectMsg("pong") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /akka-cluster/src/main/resources/sharding.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | 4 | actor { 5 | provider = "akka.cluster.ClusterActorRefProvider" 6 | } 7 | 8 | remote { 9 | log-remote-lifecycle-events = off 10 | netty.tcp { 11 | hostname = "127.0.0.1" 12 | port = 0 13 | } 14 | } 15 | 16 | cluster { 17 | seed-nodes = [ 18 | "akka.tcp://ClusterSystem@127.0.0.1:2551", 19 | "akka.tcp://ClusterSystem@127.0.0.1:2552"] 20 | 21 | auto-down-unreachable-after = 10s 22 | } 23 | 24 | persistence { 25 | journal.plugin = "akka.persistence.journal.leveldb-shared" 26 | journal.leveldb-shared.store { 27 | # DO NOT USE 'native = off' IN PRODUCTION !!! 28 | native = off 29 | dir = "target/shared-journal" 30 | } 31 | snapshot-store.local.dir = "target/snapshots" 32 | } 33 | } -------------------------------------------------------------------------------- /akka-routing/src/main/scala/org/elu/akka/RouterApp.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Props, ActorSystem} 4 | import org.elu.akka.Worker.Work 5 | 6 | /** Created by luhtonen on 12/04/16. */ 7 | object RouterApp extends App { 8 | 9 | val system = ActorSystem("router") 10 | 11 | system.actorOf(Props[Worker], "w1") 12 | system.actorOf(Props[Worker], "w2") 13 | system.actorOf(Props[Worker], "w3") 14 | system.actorOf(Props[Worker], "w4") 15 | system.actorOf(Props[Worker], "w5") 16 | 17 | val workers: List[String] = List( 18 | "/user/w1", 19 | "/user/w2", 20 | "/user/w3", 21 | "/user/w4", 22 | "/user/w5" 23 | ) 24 | val routesGroup = system.actorOf(Props(classOf[RouterGroup], workers)) 25 | 26 | routesGroup ! Work() 27 | routesGroup ! Work() 28 | 29 | Thread.sleep(100) 30 | 31 | system.terminate() 32 | } 33 | -------------------------------------------------------------------------------- /akka-streams/src/main/scala/org/elu/akka/Stream.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import akka.stream.ActorMaterializer 5 | import akka.stream.scaladsl.{Sink, Flow, Source} 6 | 7 | import scala.concurrent.Await 8 | import scala.concurrent.duration._ 9 | 10 | import scala.concurrent.ExecutionContext.Implicits.global 11 | 12 | /** Created by luhtonen on 18/04/16. */ 13 | object Stream extends App { 14 | implicit val actorSystem = ActorSystem() 15 | implicit val flowMaterializer = ActorMaterializer() 16 | 17 | // Source 18 | val input = Source(1 to 100) 19 | 20 | // Flow 21 | val normalize = Flow[Int].map(_ * 2) 22 | 23 | // Sink 24 | val output = Sink.foreach[Int](println) 25 | 26 | input.via(normalize).runWith(output).andThen { 27 | case _ => 28 | Await.result(actorSystem.terminate(), 10.seconds) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /reactive-tweets/src/main/scala/org/elu/akka/ReactiveTweets.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import akka.stream.ActorMaterializer 5 | import akka.stream.scaladsl.{Sink, Flow, Source} 6 | import twitter4j.Status 7 | 8 | import scala.concurrent.ExecutionContext.Implicits.global 9 | 10 | /** Created by luhtonen on 18/04/16. */ 11 | object ReactiveTweets extends App { 12 | implicit val actorSystem = ActorSystem() 13 | implicit val flowMaterializer = ActorMaterializer() 14 | 15 | val source = Source.fromIterator(() => TwitterClient.retrieveTweets("#Akka")) 16 | 17 | val normalize = Flow[Status].map { t => 18 | Tweet(Author(t.getUser.getName), t.getText) 19 | } 20 | val sink = Sink.foreach[Tweet](println) 21 | 22 | source.via(normalize).runWith(sink).andThen { 23 | case _ => 24 | actorSystem.terminate() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /akka-routing/src/main/scala/org/elu/akka/Router.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{ActorSystem, Props, ActorRef, Actor} 4 | import org.elu.akka.Worker.Work 5 | 6 | /** Created by luhtonen on 12/04/16. */ 7 | class Router extends Actor { 8 | 9 | var routees: List[ActorRef] = _ 10 | 11 | override def preStart() = { 12 | routees = List.fill(5){ 13 | context.actorOf(Props[Worker]) 14 | } 15 | } 16 | 17 | def receive() = { 18 | case msg: Work => 19 | println("I'm A Router and received a Message.....") 20 | routees(util.Random.nextInt(routees.size)) forward msg 21 | } 22 | } 23 | 24 | object Router extends App { 25 | val system = ActorSystem("router") 26 | val router = system.actorOf(Props[Router]) 27 | 28 | router ! Work() 29 | router ! Work() 30 | router ! Work() 31 | 32 | Thread.sleep(100) 33 | 34 | system.terminate() 35 | } -------------------------------------------------------------------------------- /balancing-workload/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 9: Working with Common Patterns in Akka 2 | ## Balancing Workload Across Nodes 3 | Example on how to create workload balancing for Akka actors across all nodes. It implements Master/Worker pattern, where Worker actors may run on same node or on different nodes. 4 | 5 | To see this pattern in action run `testkit`: 6 | 7 | sbt test 8 | 9 | ### Note regarding deprecated code 10 | In `Worker` class was used deprecated code to get master actor reference from `ActorPath`: 11 | 12 | val master = context.actorFor(masterLocation) 13 | 14 | `actorFor` method is deprecated and `actorSelection` should be used instead: 15 | 16 | val master = context.actorSelection(masterLocation) 17 | 18 | Here can be found more detailed description about [Balancing Workload Across Nodes with Akka 2](http://letitcrash.com/post/29044669086/balancing-workload-across-nodes-with-akka-2). -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/loadBalancing/Backend.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.loadBalancing 2 | 3 | import akka.actor.{Props, ActorSystem, Actor} 4 | import com.typesafe.config.ConfigFactory 5 | import org.elu.akka.commons.Add 6 | 7 | /** Created by luhtonen on 13/04/16. */ 8 | class Backend extends Actor { 9 | def receive = { 10 | case Add(num1, num2) => 11 | println(s"I'm a backend with path: ${self} and I received add operation.") 12 | } 13 | } 14 | 15 | object Backend { 16 | def initiate(port: Int){ 17 | val config = ConfigFactory.parseString(s"akka.remote.netty.tcp.port=$port"). 18 | withFallback(ConfigFactory.parseString("akka.cluster.roles = [backend]")). 19 | withFallback(ConfigFactory.load("loadbalancer")) 20 | 21 | val system = ActorSystem("ClusterSystem", config) 22 | 23 | val Backend = system.actorOf(Props[Backend], name = "backend") 24 | } 25 | } -------------------------------------------------------------------------------- /persistent-fsm/src/main/scala/org/elu/akka/Reporter.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.NotUsed 4 | import akka.actor.ActorSystem 5 | import akka.persistence.query.journal.leveldb.scaladsl.LeveldbReadJournal 6 | import akka.persistence.query.{PersistenceQuery, EventEnvelope} 7 | import akka.stream.ActorMaterializer 8 | import akka.stream.scaladsl.Source 9 | 10 | /** Created by luhtonen on 12/04/16. */ 11 | object Reporter extends App { 12 | 13 | val system = ActorSystem("persistent-query") 14 | implicit val mat = ActorMaterializer()(system) 15 | 16 | val queries = PersistenceQuery(system).readJournalFor[LeveldbReadJournal]( 17 | LeveldbReadJournal.Identifier 18 | ) 19 | 20 | val evts: Source[EventEnvelope, NotUsed] = queries.eventsByPersistenceId("account") 21 | 22 | evts.runForeach { evt => println(s"Event $evt")} 23 | 24 | Thread.sleep(1000) 25 | system.terminate() 26 | } 27 | -------------------------------------------------------------------------------- /akka-routing/src/main/scala/org/elu/akka/RouterPool.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Props, ActorRef, Actor} 4 | import org.elu.akka.Worker.Work 5 | 6 | /** Created by luhtonen on 12/04/16. */ 7 | class RouterPool extends Actor { 8 | 9 | var routees: List[ActorRef] = _ 10 | 11 | override def preStart() = { 12 | routees = List.fill(5){ 13 | context.actorOf(Props[Worker]) 14 | } 15 | } 16 | 17 | def receive() = { 18 | case msg: Work => 19 | println("I'm A Router and received a Message.....") 20 | routees(util.Random.nextInt(routees.size)) forward msg 21 | } 22 | } 23 | 24 | class RouterGroup(routees: List[String]) extends Actor { 25 | def receive = { 26 | case msg: Work => 27 | println(s"I'm a Router Group and I receive Work Message....") 28 | context.actorSelection(routees(util.Random.nextInt(routees.size))) forward msg 29 | } 30 | } -------------------------------------------------------------------------------- /akka-cluster/src/main/resources/singleton.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | 4 | actor { 5 | provider = "akka.cluster.ClusterActorRefProvider" 6 | } 7 | 8 | remote { 9 | log-remote-lifecycle-events = off 10 | netty.tcp { 11 | hostname = "127.0.0.1" 12 | port = 0 13 | } 14 | } 15 | 16 | cluster { 17 | seed-nodes = [ 18 | "akka.tcp://ClusterSystem@127.0.0.1:2551", 19 | "akka.tcp://ClusterSystem@127.0.0.1:2552"] 20 | 21 | auto-down-unreachable-after = 10s 22 | } 23 | 24 | persistence { 25 | journal.plugin = "akka.persistence.journal.leveldb-shared" 26 | journal.leveldb-shared.store { 27 | # DO NOT USE 'native = off' IN PRODUCTION !!! 28 | native = off 29 | dir = "target/shared-journal" 30 | } 31 | snapshot-store.plugin = "akka.persistence.snapshot-store.local" 32 | snapshot-store.local.dir = "target/snapshots" 33 | } 34 | } -------------------------------------------------------------------------------- /testing-parent-child/src/test/scala/org/elu/akka/ParentSpec.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Props, ActorRefFactory, ActorSystem} 4 | import akka.testkit.{TestProbe, ImplicitSender, TestKit} 5 | import org.scalatest.{MustMatchers, BeforeAndAfterAll, FlatSpecLike} 6 | 7 | /** Created by luhtonen on 18/04/16. */ 8 | class ParentSpec extends TestKit(ActorSystem("test-system")) 9 | with ImplicitSender 10 | with FlatSpecLike 11 | with BeforeAndAfterAll 12 | with MustMatchers { 13 | 14 | override def afterAll = { 15 | TestKit.shutdownActorSystem(system) 16 | } 17 | 18 | "Parent" should "send pint message to child when receive ping message" in { 19 | val child = TestProbe() 20 | val childMaker = (_: ActorRefFactory) => child.ref 21 | val parent = system.actorOf(Props(new Parent(childMaker))) 22 | 23 | parent ! "ping" 24 | 25 | child.expectMsg("ping") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /playing-with-actors/src/main/scala/org/elu/akka/Monitoring.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor._ 4 | 5 | class Ares(athena: ActorRef) extends Actor { 6 | 7 | override def preStart() = { 8 | context.watch(athena) 9 | } 10 | 11 | override def postStop() = { 12 | println("Ares postStop...") 13 | } 14 | 15 | def receive = { 16 | case Terminated => 17 | context.stop(self) 18 | } 19 | } 20 | 21 | class Athena extends Actor { 22 | 23 | def receive = { 24 | case msg => 25 | println(s"Athena received ${msg}") 26 | context.stop(self) 27 | } 28 | } 29 | 30 | object Monitoring extends App { 31 | // Create the 'monitoring' actor system 32 | val system = ActorSystem("monitoring") 33 | 34 | val athena = system.actorOf(Props[Athena], "athena") 35 | 36 | val ares = system.actorOf(Props(classOf[Ares], athena), "ares") 37 | 38 | athena ! "Hi" 39 | 40 | system.terminate() 41 | } -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/singleton/Frontend.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.singleton 2 | 3 | import scala.concurrent.duration._ 4 | import akka.actor.{Actor, ActorLogging, Props} 5 | import akka.cluster.singleton.{ClusterSingletonProxySettings, ClusterSingletonProxy} 6 | 7 | /** Created by luhtonen on 13/04/16. */ 8 | class Frontend extends Actor with ActorLogging { 9 | import Frontend._ 10 | import context.dispatcher 11 | 12 | val masterProxy = context.actorOf(ClusterSingletonProxy.props( 13 | singletonManagerPath = "/user/master", 14 | settings = ClusterSingletonProxySettings(context.system).withRole(None) 15 | ), name = "masterProxy") 16 | 17 | context.system.scheduler.schedule(0.second, 3.second, self, Tick) 18 | 19 | def receive = { 20 | case Tick => 21 | masterProxy ! Master.Work(self, "add") 22 | } 23 | } 24 | 25 | object Frontend { 26 | case object Tick 27 | def props = Props(new Frontend()) 28 | } -------------------------------------------------------------------------------- /akka-cluster/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | Frontend { 2 | akka { 3 | actor { 4 | provider = "akka.cluster.ClusterActorRefProvider" 5 | } 6 | remote { 7 | log-remote-lifecycle-events = off 8 | netty.tcp { 9 | hostname = "127.0.0.1" 10 | port = 2551 11 | } 12 | } 13 | cluster { 14 | roles = ["frontend"] 15 | seed-nodes = ["akka.tcp://ClusterSystem@127.0.0.1:2551"] 16 | auto-down-unreachable-after = 10s 17 | } 18 | } 19 | } 20 | 21 | Backend { 22 | akka { 23 | actor { 24 | provider = "akka.cluster.ClusterActorRefProvider" 25 | } 26 | remote { 27 | log-remote-lifecycle-events = off 28 | netty.tcp { 29 | hostname = "127.0.0.1" 30 | port = 0 31 | } 32 | } 33 | cluster { 34 | roles = ["backend"] 35 | seed-nodes = ["akka.tcp://ClusterSystem@127.0.0.1:2551"] 36 | auto-down-unreachable-after = 10s 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /actor-paths/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 3: Working with Akka basic tools 2 | ## ActorRef Versus Actor Path Versus Actor Selection 3 | In this chapter exploring differences between `ActorRef`, actor path and selection. When actor is created for example with `system.actorOf` actually reference to this actor is returned `ActorRef`. 4 | 5 | The method `actorSelection()` returns an `ActorSelection` rather than an `ActorRef`. An `ActorSelection` can be used to send a message to the actor referenced by it. However using this approach is slower and more resource intensive to resolve than using an `ActorRef`. Yet, `actorSelection()` is a nice facility because it can accept wildcard actor queries, which when resolved allows you to broadcast a message to any number of actors represented by the `ActorSelection`. 6 | 7 | Interesting line in file `App.scala`: 8 | 9 | counter1 ! PoisonPill 10 | 11 | `PoisonPill` is a message all Actors will understand, that when processed will terminate the Actor permanently. -------------------------------------------------------------------------------- /akka-routing/src/main/scala/org/elu/akka/RoundRobin.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Props, ActorSystem} 4 | import akka.routing.{FromConfig, RoundRobinPool} 5 | import org.elu.akka.Worker.Work 6 | 7 | /** Created by luhtonen on 12/04/16. */ 8 | object RoundRobin extends App { 9 | 10 | val system = ActorSystem("Round-Robin-Router") 11 | val routerPool = system.actorOf(RoundRobinPool(3).props(Props[Worker]), "round-robin-pool") 12 | 13 | routerPool ! Work() 14 | routerPool ! Work() 15 | routerPool ! Work() 16 | routerPool ! Work() 17 | 18 | Thread.sleep(100) 19 | 20 | system.actorOf(Props[Worker], "w1") 21 | system.actorOf(Props[Worker], "w2") 22 | system.actorOf(Props[Worker], "w3") 23 | 24 | val routerGroup = system.actorOf(FromConfig.props(), "round-robin-group") 25 | 26 | routerGroup ! Work() 27 | routerGroup ! Work() 28 | routerGroup ! Work() 29 | routerGroup ! Work() 30 | 31 | Thread.sleep(100) 32 | system.terminate() 33 | } 34 | -------------------------------------------------------------------------------- /throttler-messages/src/main/scala/org/elu/akka/ThrottlerApp.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import java.util.Date 4 | 5 | import akka.actor.{Props, Actor, ActorSystem} 6 | import org.elu.akka.throttle.TimerBasedThrottler 7 | import scala.concurrent.duration._ 8 | 9 | class Target extends Actor { 10 | def receive = { 11 | case msg => 12 | println(s"[${new Date().toString}}] I receive msg: $msg") 13 | } 14 | } 15 | 16 | /** Created by luhtonen on 27/04/16. */ 17 | object ThrottlerApp extends App { 18 | import org.elu.akka.throttle.Throttler._ 19 | 20 | val system = ActorSystem("Thottler-Messages") 21 | val target = system.actorOf(Props[Target], "target") 22 | val throttler = system.actorOf(Props(classOf[TimerBasedThrottler], 3 msgsPer 1.second)) 23 | 24 | throttler ! SetTarget(Some(target)) 25 | 26 | throttler ! "1" 27 | throttler ! "2" 28 | throttler ! "3" 29 | throttler ! "4" 30 | throttler ! "5" 31 | 32 | Thread.sleep(5000) 33 | system.terminate() 34 | } 35 | -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/singleton/Worker.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.singleton 2 | 3 | import scala.concurrent.duration._ 4 | import akka.actor.{Actor, ActorLogging, Props} 5 | import akka.cluster.singleton.{ClusterSingletonProxySettings, ClusterSingletonProxy} 6 | 7 | /** Created by luhtonen on 13/04/16. */ 8 | class Worker extends Actor with ActorLogging{ 9 | import Master._ 10 | import context.dispatcher 11 | 12 | val masterProxy = context.actorOf(ClusterSingletonProxy.props( 13 | singletonManagerPath = "/user/master", 14 | settings = ClusterSingletonProxySettings(context.system).withRole(None) 15 | ), name = "masterProxy") 16 | 17 | context.system.scheduler.schedule(0.second, 30.second, masterProxy, RegisterWorker(self)) 18 | context.system.scheduler.schedule(3.second, 3.second, masterProxy, RequestWork(self)) 19 | 20 | def receive = { 21 | case Work(requester, op) => 22 | log.info(s"Worker: I received work with op: $op and I will reply to $requester.") 23 | } 24 | } 25 | 26 | object Worker { 27 | def props = Props(new Worker()) 28 | } -------------------------------------------------------------------------------- /stream-io/src/main/scala/org/elu/akka/ReadStream.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import java.io.File 4 | 5 | import akka.actor.ActorSystem 6 | import akka.stream.ActorMaterializer 7 | import akka.stream.scaladsl.{Sink, Framing, FileIO} 8 | import akka.util.ByteString 9 | 10 | import scala.concurrent.Await 11 | import scala.concurrent.duration._ 12 | 13 | /** Created by luhtonen on 21/04/16. */ 14 | object ReadStream extends App { 15 | import scala.concurrent.ExecutionContext.Implicits.global 16 | implicit val actorSystem = ActorSystem() 17 | implicit val materializer = ActorMaterializer() 18 | 19 | // read lines from a log file 20 | val logFile = new File("src/main/resources/log.txt") 21 | val source = FileIO.fromFile(logFile) 22 | // parse chunks of bytes into lines 23 | val flow = Framing.delimiter(ByteString(System.lineSeparator()), 24 | maximumFrameLength = 512, allowTruncation = true).map(_.utf8String) 25 | val sink = Sink.foreach(println) 26 | 27 | source.via(flow).runWith(sink).andThen { 28 | case _ => Await.result(actorSystem.terminate(), 10.seconds) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /akka-routing/src/main/scala/org/elu/akka/Random.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Props, ActorSystem} 4 | import akka.routing.{RandomGroup, FromConfig} 5 | import org.elu.akka.Worker.Work 6 | 7 | /** Created by luhtonen on 12/04/16. */ 8 | object Random extends App { 9 | 10 | println("### Random Router Pool") 11 | val system = ActorSystem("Random-Router") 12 | 13 | val routerPool = system.actorOf(FromConfig.props(Props[Worker]), "random-router-pool") 14 | 15 | routerPool ! Work() 16 | routerPool ! Work() 17 | routerPool ! Work() 18 | routerPool ! Work() 19 | 20 | Thread.sleep(100) 21 | 22 | println("### Random Router Group") 23 | 24 | system.actorOf(Props[Worker], "w1") 25 | system.actorOf(Props[Worker], "w2") 26 | system.actorOf(Props[Worker], "w3") 27 | 28 | val paths = List("/user/w1", "/user/w2", "/user/w3") 29 | 30 | val routerGroup = system.actorOf(RandomGroup(paths).props(), "random-router-group") 31 | 32 | routerGroup ! Work() 33 | routerGroup ! Work() 34 | routerGroup ! Work() 35 | routerGroup ! Work() 36 | 37 | Thread.sleep(100) 38 | system.terminate() 39 | } 40 | -------------------------------------------------------------------------------- /persistence/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 4: Akka Persistence 2 | ## Creating Persistent Actors 3 | 4 | In this chapter experimenting with Akka Persistence, which requires extra packages to be added to the project `libraryDependencies` in `build.sbt` file: 5 | 6 | "com.typesafe.akka" %% "akka-persistence" % "2.4.3", 7 | "org.iq80.leveldb" % "leveldb" % "0.7", 8 | "org.fusesource.leveldbjni" % "leveldbjni-all" % "1.8" 9 | 10 | Application configuration should be also added. `application.conf` file should be created into `src/main/resources` directory: 11 | 12 | akka { 13 | persistence { 14 | journal { 15 | plugin = "akka.persistence.journal.leveldb", 16 | leveldb { 17 | dir = "target/example/journal", 18 | native = false 19 | } 20 | }, 21 | snapshot-store { 22 | plugin = "akka.persistence.snapshot-store.local", 23 | local { 24 | dir = "target/example/snapshots" 25 | } 26 | } 27 | } 28 | } 29 | 30 | ## Playing with a Persistent Actor 31 | In this section described how to handle recovery process completion and how to create actor state shapshot. -------------------------------------------------------------------------------- /akka-remoting/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | MembersService { 2 | akka { 3 | actor { 4 | provider = "akka.remote.RemoteActorRefProvider" 5 | } 6 | remote { 7 | enabled-transports = ["akka.remote.netty.tcp"] 8 | netty.tcp { 9 | hostname = "127.0.0.1" 10 | port = 2552 11 | } 12 | } 13 | } 14 | } 15 | 16 | MemberServiceLookup { 17 | akka { 18 | actor { 19 | provider = "akka.remote.RemoteActorRefProvider" 20 | } 21 | remote { 22 | enabled-transports = ["akka.remote.netty.tcp"] 23 | netty.tcp { 24 | hostname = "127.0.0.1" 25 | port = 2553 26 | } 27 | } 28 | } 29 | } 30 | 31 | MembersServiceRemoteCreation { 32 | akka { 33 | actor { 34 | provider = "akka.remote.RemoteActorRefProvider" 35 | deployment { 36 | /workerActorRemote { 37 | remote: "akka.tcp://MembersService@127.0.0.1:2552" 38 | } 39 | } 40 | } 41 | remote { 42 | enabled-transports = ["akka.remote.netty.tcp"] 43 | netty.tcp { 44 | hostname = "127.0.0.1" 45 | port = 2558 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /akka-routing/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 3 : Working with Akka Basic Tools 2 | ## Sending Messages via Router 3 | Here can be found examples of actor routers, router pools and router groups, as well as demonstration of the Random routing strategy. 4 | 5 | ### RoundRobin routing strategy 6 | In the video mention many different routing strategies. RoundRobin is one of those strategies. Code example can be found from `RoundRobin.scala`. 7 | 8 | Router pool is created as following: 9 | 10 | val routerPool = system.actorOf(RoundRobinPool(3).props(Props[Worker]), "round-robin-pool") 11 | 12 | This creates pool of 3 workers from `Worker` props with RoundRobin strategy and named as `round-robin-pool`. 13 | 14 | Router group is created based on the configurations defined in `application.conf` file: 15 | 16 | /round-robin-group { 17 | router = round-robin-group 18 | routees.paths = ["/user/w1", "/user/w2", "/user/w3"] 19 | } 20 | 21 | as following: 22 | 23 | val routerGroup = system.actorOf(FromConfig.props(), "round-robin-group") 24 | 25 | More information about routers and routing strategies from [Akka official documentation](http://doc.akka.io/docs/akka/2.4.0/scala/routing.html). -------------------------------------------------------------------------------- /graph-flows/src/main/scala/org/elu/akka/GraphFlow.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.NotUsed 4 | import akka.actor.ActorSystem 5 | import akka.stream.scaladsl._ 6 | import akka.stream.{ActorMaterializer, ClosedShape} 7 | 8 | import scala.concurrent.Await 9 | import scala.concurrent.duration._ 10 | 11 | /** Created by luhtonen on 21/04/16. */ 12 | object GraphFlow extends App { 13 | implicit val actorSystem = ActorSystem() 14 | implicit val flowMaterializer = ActorMaterializer() 15 | 16 | val in = Source(1 to 10) 17 | val out = Sink.foreach[Int](println) 18 | 19 | val f1, f3 = Flow[Int].map(_ + 10) 20 | val f2 = Flow[Int].map(_ * 5) 21 | val f4 = Flow[Int].map(_ + 0) 22 | 23 | val g = RunnableGraph.fromGraph(GraphDSL.create() { implicit builder: GraphDSL.Builder[NotUsed] => 24 | import GraphDSL.Implicits._ 25 | 26 | val broadCast = builder.add(Broadcast[Int](2)) 27 | val merge = builder.add(Merge[Int](2)) 28 | 29 | in ~> f1 ~> broadCast ~> f2 ~> merge ~> f3 ~> out 30 | broadCast ~> f4 ~> merge 31 | 32 | ClosedShape 33 | }) 34 | 35 | g.run() 36 | 37 | Thread.sleep(1000) 38 | Await.result(actorSystem.terminate(), 10.seconds) 39 | } 40 | -------------------------------------------------------------------------------- /akka-cluster/src/main/resources/loadbalancer.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | actor { 3 | provider = "akka.cluster.ClusterActorRefProvider" 4 | } 5 | remote { 6 | log-remote-lifecycle-events = off 7 | netty.tcp { 8 | hostname = "127.0.0.1" 9 | port = 0 10 | } 11 | } 12 | 13 | cluster { 14 | seed-nodes = [ 15 | "akka.tcp://ClusterSystem@127.0.0.1:2551", 16 | "akka.tcp://ClusterSystem@127.0.0.1:2552"] 17 | 18 | auto-down-unreachable-after = 10s 19 | } 20 | } 21 | 22 | akka.cluster.min-nr-of-members = 3 23 | 24 | akka.cluster.role { 25 | frontend.min-nr-of-members = 1 26 | backend.min-nr-of-members = 2 27 | } 28 | 29 | akka.actor.deployment { 30 | /frontend/backendRouter { 31 | # Router type provided by metrics extension. 32 | router = adaptive-group 33 | # Router parameter specific for metrics extension. 34 | # metrics-selector = heap 35 | # metrics-selector = load 36 | # metrics-selector = cpu 37 | metrics-selector = mix 38 | # 39 | nr-of-instances = 100 40 | routees.paths = ["/user/backend"] 41 | cluster { 42 | enabled = on 43 | use-role = backend 44 | allow-local-routees = off 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /multi-node-testing/src/multi-jvm/scala/org/elu/akka/MultiNodeSample.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.Props 4 | import akka.remote.testkit.MultiNodeSpec 5 | import akka.testkit.ImplicitSender 6 | import MultiNodeSampleConfig._ 7 | 8 | /** Created by luhtonen on 18/04/16. */ 9 | class MultiNodeSample extends MultiNodeSpec(MultiNodeSampleConfig) 10 | with BasicMultiNodeSpec 11 | with ImplicitSender { 12 | 13 | // initial Participants 14 | def initialParticipants = roles.size 15 | 16 | "A MultiNodeSample" should "wait for all nodes to enter a barrier" in { 17 | enterBarrier("startup") 18 | } 19 | 20 | it should "send to and receive from a remote node" in { 21 | runOn(node2) { 22 | system.actorOf(Props[Worker], name = "worker") 23 | enterBarrier("deployed") 24 | } 25 | 26 | runOn(node1) { 27 | enterBarrier("deployed") 28 | val worker = system.actorSelection(node(node2) / "user" / "worker") 29 | 30 | worker ! Worker.Work 31 | 32 | expectMsg(Worker.Done) 33 | } 34 | 35 | enterBarrier("finished") 36 | } 37 | } 38 | 39 | class MultiNodeSampleMultiJvmNode1 extends MultiNodeSample 40 | class MultiNodeSampleMultiJvmNode2 extends MultiNodeSample -------------------------------------------------------------------------------- /throttler-messages/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 9: Working with Common Patterns in Akka 2 | ## Throttling Messages 3 | In this example shown how to employ a *message throttler*. This example is based on a simple implementation of a throttling actor, the `TimerBasedThrottler`, which is provided by akka `contrib` module. 4 | 5 | Here's how official Akka documentation suggest to use `contrib` modules: 6 | > ### Suggested Way of Using these Contributions 7 | Since the Akka team does not restrict updates to this subproject even during otherwise binary compatible releases, and modules may be removed without deprecation, it is suggested to copy the source files into your own code base, changing the package name. This way you can choose when to update or which fixes to include (to keep binary compatibility if needed) and later releases of Akka do not potentially break your application. 8 | 9 | In this example `TimerBasedThrottler.scala` is a copy from Akka contribution. Source code for contribution modules can be found on [Akka GitHub](https://github.com/akka/akka/tree/master/akka-contrib). 10 | 11 | Here can be found more detailed description on how to use [Throttling Messages in Akka 2](http://letitcrash.com/post/28901663062/throttling-messages-in-akka-2). -------------------------------------------------------------------------------- /persistent-fsm/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 4: Akka Persistence 2 | ## Persistence FSM 3 | Akka Persistence with Finite State Machine (FSM). 4 | 5 | Following dependencies are required in `build.sbt` file: 6 | 7 | libraryDependencies ++= Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.4.3", 9 | "com.typesafe.akka" %% "akka-persistence" % "2.4.3", 10 | "org.iq80.leveldb" % "leveldb" % "0.7", 11 | "org.fusesource.leveldbjni" % "leveldbjni-all" % "1.8" 12 | ) 13 | 14 | As well as application.conf file in src/main/resources directory: 15 | 16 | akka { 17 | persistence { 18 | journal { 19 | plugin = "akka.persistence.journal.leveldb", 20 | leveldb { 21 | dir = "target/example/journal", 22 | native = false 23 | } 24 | }, 25 | snapshot-store { 26 | plugin = "akka.persistence.snapshot-store.local", 27 | local { 28 | dir = "target/example/snapshots" 29 | } 30 | } 31 | } 32 | } 33 | 34 | ## Persistence Query 35 | This is experimental feature in Akka Persistence and it requires extra dependencies: 36 | 37 | "com.typesafe.akka" %% "akka-persistence-query-experimental" % "2.4.3", 38 | "com.typesafe.akka" % "akka-stream-experimental_2.11" % "2.0.4" 39 | -------------------------------------------------------------------------------- /scheduler-messages/src/main/scala/org/elu/akka/Scheduler.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.Date 5 | 6 | import akka.actor.Actor 7 | import scala.concurrent.duration._ 8 | import scala.language.postfixOps 9 | 10 | class ScheduleInConstrutor extends Actor { 11 | import context._ 12 | 13 | val tick = context.system.scheduler.schedule(500 millis, 1.second, self, "tick") 14 | 15 | override def postStop() = tick.cancel() 16 | 17 | def receive = { 18 | case "tick" => 19 | println(s"Cool! I got tick message at ${new SimpleDateFormat().format(new Date())}") 20 | } 21 | } 22 | 23 | class ScheduleInReceive extends Actor { 24 | import context._ 25 | 26 | override def preStart() = context.system.scheduler.scheduleOnce(500 millis, self, "tick") 27 | 28 | // override postRestart so we don't call preStart and schedule a new message 29 | override def postRestart(reason: Throwable) = {} 30 | 31 | def receive = { 32 | case "tick" => 33 | println(s"Cool! I got tick message at ${new SimpleDateFormat().format(new Date())}") 34 | // send another periodic tick after the specified delay 35 | context.system.scheduler.scheduleOnce(1000 millis, self, "tick") 36 | } 37 | } -------------------------------------------------------------------------------- /shutdown-pattern/src/main/scala/org/elu/akka/pattern/Reaper.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.pattern 2 | 3 | import akka.actor.{Terminated, Actor, ActorRef} 4 | import scala.collection.mutable.ArrayBuffer 5 | 6 | object Reaper { 7 | val name = "reaper" 8 | // Used by others to register an Actor for watching 9 | case class WatchMe(ref: ActorRef) 10 | } 11 | 12 | class Reaper extends Actor { 13 | import Reaper._ 14 | 15 | // Keep track of what we're watching 16 | val watched = ArrayBuffer.empty[ActorRef] 17 | 18 | // Derivations need to implement this method. It's the 19 | // hook that's called when everything's dead 20 | def allSoulsReaped() = { 21 | println("SYSTEM SHUTDOWN START") 22 | context.system.terminate() 23 | println("SYSTEM SHUTDOWN END") 24 | } 25 | 26 | // Watch and check for termination 27 | final def receive = { 28 | case WatchMe(ref) => 29 | context.watch(ref) 30 | watched += ref 31 | case Terminated(ref) => 32 | watched -= ref 33 | if (watched.isEmpty) allSoulsReaped() 34 | } 35 | } 36 | 37 | trait ReaperWatched { this: Actor => 38 | override def preStart() { 39 | println("IN PRESTART") 40 | context.actorSelection("/user/" + Reaper.name) ! Reaper.WatchMe(self) 41 | } 42 | } -------------------------------------------------------------------------------- /akka-remoting/src/main/scala/org/elu/akka/Remote.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Props, ActorSystem} 4 | import com.typesafe.config.ConfigFactory 5 | 6 | object MembersService extends App { 7 | val config = ConfigFactory.load.getConfig("MembersService") 8 | 9 | val system = ActorSystem("MembersService", config) 10 | 11 | val worker = system.actorOf(Props[Worker], "remote-worker") 12 | 13 | println(s"Worker actor path is ${worker.path}") 14 | } 15 | 16 | object MemberServiceLookup extends App { 17 | val config = ConfigFactory.load.getConfig("MemberServiceLookup") 18 | 19 | val system = ActorSystem("MemberServiceLookup", config) 20 | 21 | val worker = system.actorSelection("akka.tcp://MembersService@127.0.0.1:2552/user/remote-worker") 22 | 23 | worker ! Worker.Work("Hi Remote Actor") 24 | } 25 | 26 | object MembersServiceRemoteCreation extends App { 27 | 28 | val config = ConfigFactory.load.getConfig("MembersServiceRemoteCreation") 29 | 30 | val system = ActorSystem("MembersServiceRemoteCreation", config) 31 | 32 | val workerActor = system.actorOf(Props[Worker], "workerActorRemote") 33 | 34 | println(s"The remote path of worker Actor is ${workerActor.path}") 35 | 36 | workerActor ! Worker.Work("Hi Remote Worker") 37 | } -------------------------------------------------------------------------------- /akka-http-server-side-api/src/main/scala/org/elu/akka/HighLevel.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import akka.http.scaladsl.Http 5 | import akka.stream.ActorMaterializer 6 | import akka.http.scaladsl.server.Directives._ 7 | 8 | import scala.concurrent.ExecutionContext.Implicits.global 9 | import scala.io.StdIn 10 | 11 | /** Created by luhtonen on 22/04/16. */ 12 | object HighLevel extends App { 13 | implicit val system = ActorSystem() 14 | implicit val materializer = ActorMaterializer() 15 | 16 | /* 17 | path(RESOURCE_PATH) { 18 | method[get | put | post...] 19 | { 20 | complete { 21 | // return your response is case you accept this request. 22 | } 23 | reject { 24 | // return your response is case you reject this request. 25 | } 26 | } 27 | } 28 | */ 29 | 30 | val route = path("") { 31 | get { 32 | complete("Hello Akka HTTP Server Side API - High Level") 33 | } 34 | } 35 | val bindingFuture = Http().bindAndHandle(route, "localhost", 8080) 36 | 37 | println(s"Server online at http://localhost:8080/\nPress RETURN to stop...") 38 | StdIn.readLine() 39 | 40 | bindingFuture 41 | .flatMap(_.unbind()) 42 | .onComplete(_ => system.terminate()) 43 | } 44 | -------------------------------------------------------------------------------- /stream-test/src/test/scala/org/elu/akka/StreamKitSpec.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import akka.stream.ActorMaterializer 5 | import akka.stream.scaladsl.{Keep, Sink, Source} 6 | import akka.stream.testkit.scaladsl.{TestSink, TestSource} 7 | import akka.testkit.{ImplicitSender, TestKit} 8 | import org.scalatest.{BeforeAndAfterAll, FlatSpecLike, MustMatchers} 9 | 10 | /** Created by luhtonen on 18/04/16. */ 11 | class StreamKitSpec extends TestKit(ActorSystem("test-system")) 12 | with ImplicitSender 13 | with FlatSpecLike 14 | with BeforeAndAfterAll 15 | with MustMatchers { 16 | 17 | override def afterAll { 18 | TestKit.shutdownActorSystem(system) 19 | } 20 | 21 | implicit val flowMaterializer = ActorMaterializer() 22 | 23 | "With Stream Test Kit" should "use a TestSink to test a source" in { 24 | val sourceUnderTest = Source(1 to 4).filter(_ < 3).map(_ * 2) 25 | 26 | sourceUnderTest.runWith(TestSink.probe[Int](system)) 27 | .request(2) 28 | .expectNext(2, 4) 29 | } 30 | 31 | it should "use a TestSource to test a sink" in { 32 | val sinkUnderTest = Sink.cancelled 33 | 34 | TestSource.probe[Int] 35 | .toMat(sinkUnderTest)(Keep.left) 36 | .run() 37 | .expectCancellation() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/cluster/Frontend.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.cluster 2 | 3 | import akka.actor._ 4 | import com.typesafe.config.ConfigFactory 5 | import org.elu.akka.commons.{BackendRegistration, Add} 6 | 7 | import scala.util.Random 8 | 9 | /** Created by luhtonen on 13/04/16. */ 10 | class Frontend extends Actor { 11 | 12 | var backends = IndexedSeq.empty[ActorRef] 13 | 14 | def receive = { 15 | case Add if backends.isEmpty => 16 | println("Service unavailable, cluster doesn't have backend node.") 17 | case addOp: Add => 18 | println("Frontend: I'll forward add operation to backend node to handle it.") 19 | backends(Random.nextInt(backends.size)) forward addOp 20 | case BackendRegistration if !backends.contains(sender()) => 21 | backends = backends :+ sender() 22 | context watch sender() 23 | case Terminated(a) => 24 | backends = backends.filterNot(_ == a) 25 | } 26 | } 27 | 28 | object Frontend { 29 | private var _frontend: ActorRef = _ 30 | 31 | def initiate() = { 32 | val config = ConfigFactory.load().getConfig("Frontend") 33 | val system = ActorSystem("ClusterSystem", config) 34 | _frontend = system.actorOf(Props[Frontend], name = "frontend") 35 | } 36 | 37 | def getFrontend = _frontend 38 | } 39 | -------------------------------------------------------------------------------- /rest-api/src/main/scala/org/elu/akka/db/TweetManager.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.db 2 | 3 | import org.elu.akka.models.TweetEntity 4 | import reactivemongo.api.collections.bson.BSONCollection 5 | import reactivemongo.bson.{BSONDocument, BSONObjectID} 6 | 7 | import scala.concurrent.{Await, ExecutionContext} 8 | import scala.concurrent.duration._ 9 | import scala.concurrent.ExecutionContext.Implicits.global 10 | 11 | /** Created by luhtonen on 22/04/16. */ 12 | object TweetManager { 13 | import MongoDB._ 14 | import TweetEntity._ 15 | 16 | val collection: BSONCollection = Await.result(db.map(_.collection("tweets")), 10.seconds) 17 | 18 | def save(tweetEntity: TweetEntity)(implicit ec: ExecutionContext) = 19 | collection.insert(tweetEntity).map(_ => Created(tweetEntity.id.stringify)) 20 | 21 | def findById(id: String)(implicit ec: ExecutionContext) = 22 | collection.find(queryById(id)).one[TweetEntity] 23 | 24 | def deleteById(id: String)(implicit ec: ExecutionContext) = 25 | collection.remove(queryById(id)).map(_ => Deleted) 26 | 27 | def find(implicit ec: ExecutionContext) = 28 | collection.find(emptyQuery).cursor[BSONDocument]().collect[List]() 29 | 30 | private def queryById(id: String) = BSONDocument("_id" -> BSONObjectID(id)) 31 | 32 | private def emptyQuery = BSONDocument() 33 | } 34 | -------------------------------------------------------------------------------- /multi-node-testing/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 6 : Testing Actors 2 | ## Multi Node Testing 3 | In this example testing actors running on different nodes and in this particular example on different JVMs. 4 | 5 | ### Changes to build confiugration 6 | Since SBT version `0.13.2` `Project.defaultSettings` is deprecated. `Defaults.coreDefaultSettings` should be used instead along with plugins configurations. 7 | 8 | ### TODO 9 | Running test produces lots of serialisation warnings, which are related to use of default Java serializer. 10 | 11 | One error is reported on system shutdown: 12 | 13 | [JVM-1] [ERROR] [04/18/2016 16:08:31.495] [MultiNodeSample-akka.remote.default-remote-dispatcher-6] [akka.tcp://MultiNodeSample@localhost:61045/system/endpointManager/reliableEndpointWriter-akka.tcp%3A%2F%2FMultiNodeSample%40localhost%3A61046-0/endpointWriter] AssociationError [akka.tcp://MultiNodeSample@localhost:61045] -> [akka.tcp://MultiNodeSample@localhost:61046]: Error [Shut down address: akka.tcp://MultiNodeSample@localhost:61046] [ 14 | [JVM-1] akka.remote.ShutDownAssociation: Shut down address: akka.tcp://MultiNodeSample@localhost:61046 15 | [JVM-1] Caused by: akka.remote.transport.Transport$InvalidAssociationException: The remote system terminated the association because it is shutting down. 16 | 17 | It need to be investigated to find nicer way to shutdown remote actors. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learning Akka 2 | This is the example code for the lessons from the Video Course [Learning Akka](https://www.safaribooksonline.com/library/view/learning-akka/9781784391836/) by Salma Khater. 3 | 4 | ## Introduction 5 | [Akka](http://akka.io/) is an open-source toolkit and runtime simplifying the construction of concurrent and distributed applications on the JVM. Akka supports multiple programming models for concurrency, but it emphasises actor-based concurrency, with inspiration drawn from Erlang. Language bindings exist for both Java and Scala. 6 | 7 | This course suppose to provide the foundation in Akka that will enable to develop excellent applications and services with this toolkit. 8 | 9 | In the README files to my examples I will discuss the issues, which I found important but which are not mentioned in the videos. 10 | 11 | ## Contents 12 | Chapter 1: [Exploring the Akka World](hello-akka) 13 | Chapter 2: [Playing with Actors](playing-with-actors) 14 | Chapter 3: [Working with Akka basic tools](akka-basic-tools.md) 15 | Chapter 4: [Akka Persistence](persistence.md) 16 | Chapter 5: [Working with Akka Cluster](akka-cluster.md) 17 | Chapter 6: [Testing Actors](testing.md) 18 | Chapter 7: [Working with Akka Streams](streams.md) 19 | Chapter 8: [Working with Akka HTTP](http.md) 20 | Chapter 9: [Working with Common Patterns in Akka](patterns.md) -------------------------------------------------------------------------------- /reactive-tweets/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 7: Working with Akka Streams 2 | ## Reactive Tweets 3 | This example shows how to stream, transform and print out tweets. 4 | 5 | ### Application configuration 6 | This example reads configuration from `application.conf` file located in `src/main/resources` directory. It contains contains Tweeter connection configuration. 7 | 8 | ### Some notes about changed API 9 | In original video Stream `Source` was created using following line of code: 10 | 11 | val source = Source(() => TwitterClient.retrieveTweets("#Akka")) 12 | 13 | In Akka Stream version `2.4.4` this was causing `type mismatch` error: 14 | 15 | [error] ReactiveTweets.scala:16: type mismatch; 16 | [error] found : () => Iterator[twitter4j.Status] 17 | [error] required: scala.collection.immutable.Iterable[?] 18 | [error] val source = Source(() => TwitterClient.retrieveTweets("#Akka")) 19 | [error] ^ 20 | [error] one error found 21 | [error] (compile:compileIncremental) Compilation failed 22 | 23 | As this error message clearly say Source expects to receive `Iterable` from `scala.collection.immutable`. `TweeterClient` is returning `Iterator` from `scala.collection`. This problem can be resolved by calling `fromIterator()` function of `Source` object. So the line above should be fixed as following: 24 | 25 | val source = Source.fromIterator(() => TwitterClient.retrieveTweets("#Akka")) 26 | -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/cluster/Backend.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.cluster 2 | 3 | import akka.actor.{Props, ActorSystem, RootActorPath, Actor} 4 | import akka.cluster.Cluster 5 | import akka.cluster.ClusterEvent.MemberUp 6 | import com.typesafe.config.ConfigFactory 7 | import org.elu.akka.commons.{Add, BackendRegistration} 8 | 9 | /** Created by luhtonen on 13/04/16. */ 10 | class Backend extends Actor { 11 | 12 | val cluster = Cluster(context.system) 13 | 14 | // subscribe to cluster changes, MemberUp 15 | // re-subscribe when restart 16 | override def preStart(): Unit = { 17 | cluster.subscribe(self, classOf[MemberUp]) 18 | } 19 | 20 | override def postStop(): Unit = { 21 | cluster.unsubscribe(self) 22 | } 23 | 24 | def receive = { 25 | case Add(num1, num2) => 26 | println(s"I'm a backend with path: ${self} and I received add operation.") 27 | case MemberUp(member) => 28 | if (member.hasRole("frontend")) { 29 | context.actorSelection(RootActorPath(member.address) / "user" / "frontend") ! 30 | BackendRegistration 31 | } 32 | } 33 | } 34 | 35 | object Backend { 36 | def initiate(port: Int) = { 37 | val config = ConfigFactory.parseString(s"akka.remote.netty.tcp.port=$port"). 38 | withFallback(ConfigFactory.load().getConfig("Backend")) 39 | val system = ActorSystem("ClusterSystem", config) 40 | val Backend = system.actorOf(Props[Backend], name = "Backend") 41 | } 42 | } -------------------------------------------------------------------------------- /akka-http-client-side-api/src/main/scala/org/elu/akka/RequestLevel.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import akka.http.scaladsl.Http 5 | import akka.http.scaladsl.model.{HttpRequest, HttpResponse} 6 | import akka.http.scaladsl.model.StatusCodes._ 7 | import akka.http.scaladsl.unmarshalling.Unmarshal 8 | import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ 9 | import akka.stream.ActorMaterializer 10 | 11 | import scala.concurrent.Future 12 | import scala.concurrent.ExecutionContext.Implicits.global 13 | 14 | /** Created by luhtonen on 21/04/16. */ 15 | object RequestLevel extends App { 16 | import JsonProtocol._ 17 | 18 | implicit val system = ActorSystem() 19 | implicit val materializer = ActorMaterializer() 20 | 21 | val responseFuture: Future[HttpResponse] = 22 | Http().singleRequest(HttpRequest(uri = "https://api.ipify.org?format=json")) 23 | 24 | responseFuture map { res => 25 | res.status match { 26 | case OK => 27 | Unmarshal(res.entity).to[IpInfo].map { info => 28 | println(s"The information for my ip is: $info") 29 | shutdown() 30 | } 31 | case _ => 32 | Unmarshal(res.entity).to[String].map { body => 33 | println(s"The response status is ${res.status} and response body is ${body}") 34 | shutdown() 35 | } 36 | } 37 | } 38 | 39 | def shutdown() = { 40 | Http().shutdownAllConnectionPools().onComplete { _ => 41 | system.terminate() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /akka-http-server-side-api/src/main/scala/org/elu/akka/LowLevel.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import akka.http.scaladsl.Http 5 | import akka.http.scaladsl.model.HttpMethods._ 6 | import akka.http.scaladsl.model.{HttpEntity, HttpRequest, HttpResponse, Uri} 7 | import akka.stream.ActorMaterializer 8 | import akka.stream.scaladsl.Sink 9 | 10 | import scala.concurrent.ExecutionContext.Implicits.global 11 | import scala.concurrent.Future 12 | import scala.io.StdIn 13 | 14 | /** Created by luhtonen on 22/04/16. */ 15 | object LowLevel extends App { 16 | implicit val system = ActorSystem() 17 | implicit val materializer = ActorMaterializer() 18 | 19 | val serverSource = Http().bind(interface = "localhost", port = 8888) 20 | val bindingFuture: Future[Http.ServerBinding] = serverSource.to(Sink.foreach {connection => 21 | println("Accepted new connection from " + connection.remoteAddress) 22 | 23 | connection handleWithSyncHandler requestHandler 24 | }).run() 25 | 26 | val requestHandler: HttpRequest => HttpResponse = { 27 | case HttpRequest(GET, Uri.Path("/"), _, _, _) => 28 | HttpResponse(entity = HttpEntity("Hello Akka HTTP Server Side API - Low Level!")) 29 | 30 | case _ => 31 | HttpResponse(404, entity = "Unknown resource!") 32 | } 33 | 34 | println(s"Server online at http://localhost:8888/\nPress RETURN to stop...") 35 | StdIn.readLine() 36 | 37 | bindingFuture 38 | .flatMap(_.unbind()) 39 | .onComplete(_ => system.terminate()) 40 | } 41 | -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/loadBalancing/Frontend.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.loadBalancing 2 | 3 | import akka.cluster.Cluster 4 | import akka.routing.FromConfig 5 | import com.typesafe.config.ConfigFactory 6 | 7 | import scala.concurrent.duration._ 8 | import akka.actor.{ActorRef, ActorSystem, Props, Actor} 9 | import org.elu.akka.commons.Add 10 | 11 | import scala.util.Random 12 | 13 | /** Created by luhtonen on 13/04/16. */ 14 | class Frontend extends Actor { 15 | import context.dispatcher 16 | 17 | val backend = context.actorOf(FromConfig.props(), name = "backendRouter") 18 | 19 | context.system.scheduler.schedule(3.seconds, 3.seconds, self, 20 | Add(Random.nextInt(100), Random.nextInt(100))) 21 | 22 | def receive = { 23 | case addOp: Add => 24 | println("Frontend: I'll forward add operation to backend node to handle it.") 25 | backend forward addOp 26 | } 27 | } 28 | 29 | object Frontend { 30 | 31 | private var _frontend: ActorRef = _ 32 | 33 | val upToN = 200 34 | 35 | def initiate() = { 36 | val config = ConfigFactory.parseString("akka.cluster.roles = [frontend]"). 37 | withFallback(ConfigFactory.load("loadbalancer")) 38 | 39 | val system = ActorSystem("ClusterSystem", config) 40 | system.log.info("Frontend will start when 2 backend members in the cluster.") 41 | //#registerOnUp 42 | Cluster(system) registerOnMemberUp { 43 | _frontend = system.actorOf(Props[Frontend], 44 | name = "frontend") 45 | } 46 | //#registerOnUp 47 | } 48 | def getFrontend = _frontend 49 | } -------------------------------------------------------------------------------- /http.md: -------------------------------------------------------------------------------- 1 | # Chapter 8: Working with Akka HTTP 2 | In this chapter is explained how to work with Akka HTTP. 3 | 4 | Here's what official [Akka HTTP documentation](http://doc.akka.io/docs/akka/current/scala/http/introduction.html) says: 5 | > The Akka HTTP modules implement a full server- and client-side HTTP stack on top of *akka-actor* and *akka-stream*. It's not a web-framework but rather a more general toolkit for providing and consuming HTTP-based services. While interaction with a browser is of course also in scope it is not the primary focus of Akka HTTP. 6 | 7 | Akka HTTP core (`akka-http-core`) is a release level, but following experimental packages are still in use: `akka-http-experimental`, `akka-http-jackson-experimental`, `akka-http-spray-json-experimental` and `akka-http-xml-experimental`. 8 | 9 | ## Contents 10 | [Working with Client-side API](akka-http-client-side-api) 11 | [Working with Server-side API](akka-http-server-side-api) 12 | [Let's Implement a REST API](rest-api) 13 | [Let's Test Our REST API](rest-api) 14 | 15 | ## Projects dependencies for Akka HTTP 16 | To be able to build and run examples in this chapter, at least following dependencies should be defined in `build.sbt` file: 17 | 18 | libraryDependencies ++= Seq( 19 | "com.typesafe.akka" %% "akka-actor" % "2.4.4", 20 | "com.typesafe.akka" %% "akka-stream" % "2.4.4", 21 | "com.typesafe.akka" %% "akka-http-core" % "2.4.4", 22 | "com.typesafe.akka" %% "akka-http-experimental" % "2.4.4", 23 | "com.typesafe.akka" %% "akka-http-spray-json-experimental" % "2.4.4" 24 | ) 25 | 26 | -------------------------------------------------------------------------------- /stream-test/src/test/scala/org/elu/akka/SimpleStreamSpec.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import akka.stream.ActorMaterializer 5 | import akka.stream.scaladsl.{Flow, Source, Sink} 6 | import akka.testkit.{TestKit, ImplicitSender} 7 | import org.scalatest.{FlatSpecLike, BeforeAndAfterAll, MustMatchers} 8 | 9 | import scala.concurrent.Await 10 | import scala.concurrent.duration._ 11 | 12 | /** Created by luhtonen on 18/04/16. */ 13 | class SimpleStreamSpec extends TestKit(ActorSystem("test-system")) 14 | with ImplicitSender 15 | with FlatSpecLike 16 | with BeforeAndAfterAll 17 | with MustMatchers { 18 | 19 | override def afterAll { 20 | TestKit.shutdownActorSystem(system) 21 | } 22 | 23 | implicit val flowMaterializer = ActorMaterializer() 24 | 25 | "Simple Sink" should "return the correct results" in { 26 | val sinkUnderTest = Sink.fold[Int, Int](0)(_ + _) 27 | val source = Source(1 to 4) 28 | val result = source.runWith(sinkUnderTest) 29 | 30 | Await.result(result, 100.millis) must equal(10) 31 | } 32 | 33 | "Simple Source" should "contains a correct elements" in { 34 | val source = Source(1 to 10) 35 | val result = source.grouped(2).runWith(Sink.head) 36 | 37 | Await.result(result, 100.millis) must equal(1 to 2) 38 | } 39 | 40 | "Simple Flow" should "do right transformation" in { 41 | val flow = Flow[Int].takeWhile(_ < 5) 42 | val result = Source(1 to 10).via(flow).runWith(Sink.fold(Seq.empty[Int])(_ :+ _)) 43 | 44 | Await.result(result, 100.millis) must equal(1 to 4) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /playing-with-actors/src/main/scala/org/elu/akka/ActorCreation.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{ActorSystem, Props, Actor} 4 | import org.elu.akka.MusicController.{Stop, Play} 5 | import org.elu.akka.MusicPlayer.{StartMusic, StopMusic} 6 | 7 | // Music Controller Messages 8 | object MusicController { 9 | sealed trait ControllerMsg 10 | case object Play extends ControllerMsg 11 | case object Stop extends ControllerMsg 12 | 13 | def props = Props[MusicController] 14 | } 15 | 16 | // Music Controller 17 | class MusicController extends Actor { 18 | def receive = { 19 | case Play => println("Music Started .....") 20 | case Stop => println("Music Stopped .....") 21 | } 22 | } 23 | 24 | // Music Player Messages 25 | object MusicPlayer { 26 | sealed trait PlayMsg 27 | case object StopMusic extends PlayMsg 28 | case object StartMusic extends PlayMsg 29 | } 30 | 31 | // Music Player 32 | class MusicPlayer extends Actor { 33 | def receive = { 34 | case StopMusic => println("I don't want to stop music") 35 | case StartMusic => 36 | val controller = context.actorOf(MusicController.props, "controller") 37 | controller ! Play 38 | case _ => println("Unknown Message") 39 | } 40 | } 41 | 42 | object Creation extends App { 43 | // Create the 'creation' actor system 44 | val system = ActorSystem("creation") 45 | 46 | // Create the 'MusicPlayer' actor 47 | val player = system.actorOf(Props[MusicPlayer], "player") 48 | 49 | // Send StartMusic Message to actor 50 | player ! StartMusic 51 | 52 | // shutdown system 53 | system.terminate() 54 | } -------------------------------------------------------------------------------- /streams.md: -------------------------------------------------------------------------------- 1 | # Chapter 7: Working with Akka Streams 2 | Akka Stream is now part of standard Akka package and should be imported in `build.sbt` file as following: 3 | 4 | libraryDependencies ++= Seq( 5 | "com.typesafe.akka" %% "akka-stream" % "2.4.4" 6 | ) 7 | 8 | ## Contents 9 | [Introduction to Akka Streams](akka-streams) 10 | [Reactive Tweets](reactive-tweets) 11 | [Testing Streams](stream-test) 12 | [Working with Graphs](graph-flows) 13 | [Working with Stream IO](stream-io) 14 | 15 | ### Notes regarding deprecated functions 16 | Following methods `actorSystem.shutdown()` and `actorSystem.awaitTermination()` are deprecated in Akka version `2.4.x`. So following code: 17 | 18 | actorSystem.shutdown() 19 | actorSystem.awaitTermination() 20 | 21 | Should be replaced with the following code: 22 | 23 | import scala.concurrent.Await 24 | import scala.concurrent.duration._ 25 | 26 | Await.result(actorSystem.terminate(), 10.seconds) 27 | 28 | As well as following import is not working in newer version of Akka: 29 | 30 | import system.dispatcher 31 | 32 | Following import should be used instead: 33 | 34 | import scala.concurrent.ExecutionContext.Implicits.global 35 | 36 | ### Official migration guides 37 | Akka Streams went through experimental releases from `1.0` to `2.0.2` and finally ended up in official Akka release at the time of writing `2.4.4`. 38 | 39 | [Migration Guide 1.0 to 2.x](http://doc.akka.io/docs/akka-stream-and-http-experimental/2.0.2/scala/migration-guide-1.0-2.x-scala.html) 40 | [Migration Guide 2.0.x to 2.4.x](http://doc.akka.io/docs/akka/2.4.4/scala/stream/migration-guide-2.0-2.4-scala.html) -------------------------------------------------------------------------------- /multi-node-testing/build.sbt: -------------------------------------------------------------------------------- 1 | import com.typesafe.sbt.SbtMultiJvm 2 | import com.typesafe.sbt.SbtMultiJvm.MultiJvmKeys.MultiJvm 3 | 4 | val akkaVersion = "2.4.3" 5 | 6 | val project = Project( 7 | id = "akka-sample-multi-node-scala", 8 | base = file("."), 9 | settings = Defaults.coreDefaultSettings ++ SbtMultiJvm.multiJvmSettings ++ Seq( 10 | name := "multi-node-testing", 11 | version := "1.0", 12 | scalaVersion := "2.11.8", 13 | sbtVersion := "0.13.11", 14 | libraryDependencies ++= Seq( 15 | "com.typesafe.akka" %% "akka-remote" % akkaVersion, 16 | "com.typesafe.akka" %% "akka-multi-node-testkit" % akkaVersion, 17 | "org.scalatest" %% "scalatest" % "2.2.6" 18 | ), 19 | // make sure that MultiJvm test are compiled by the default test compilation 20 | compile in MultiJvm <<= (compile in MultiJvm) triggeredBy (compile in Test), 21 | // disable parallel tests 22 | parallelExecution in Test := false, 23 | // make sure that MultiJvm tests are executed by the default test target, 24 | // and combine the results from ordinary test and multi-jvm tests 25 | executeTests in Test <<= (executeTests in Test, executeTests in MultiJvm) map { 26 | case (testResults, multiNodeResults) => 27 | val overall = 28 | if (testResults.overall.id < multiNodeResults.overall.id) 29 | multiNodeResults.overall 30 | else 31 | testResults.overall 32 | Tests.Output(overall, 33 | testResults.events ++ multiNodeResults.events, 34 | testResults.summaries ++ multiNodeResults.summaries) 35 | } 36 | ) 37 | ) configs MultiJvm 38 | 39 | -------------------------------------------------------------------------------- /hotswap-behavior/src/main/scala/org/elu/akka/Become.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Stash, ActorSystem, Props, Actor} 4 | import org.elu.akka.UserStorage.{Operation, Disconnect, Connect} 5 | 6 | case class User(username: String, email: String) 7 | 8 | object UserStorage { 9 | trait DBOperation 10 | object DBOperation { 11 | case object Create extends DBOperation 12 | case object Update extends DBOperation 13 | case object Read extends DBOperation 14 | case object Delete extends DBOperation 15 | } 16 | 17 | case object Connect 18 | case object Disconnect 19 | case class Operation(dBOperation: DBOperation, user: Option[User]) 20 | } 21 | 22 | class UserStorage extends Actor with Stash { 23 | 24 | def receive = disconnected 25 | 26 | def connected: Actor.Receive = { 27 | case Disconnect => 28 | println("User Storage disconnect from DB") 29 | context.unbecome() 30 | case Operation(op, user) => 31 | println(s"User Storage receive ${op} to do in user: ${user}") 32 | } 33 | 34 | def disconnected: Actor.Receive = { 35 | case Connect => 36 | println(s"User Storage connected to DB") 37 | unstashAll() 38 | context.become(connected) 39 | case _ => 40 | stash() 41 | } 42 | } 43 | 44 | object BecomeHotswap extends App { 45 | import UserStorage._ 46 | 47 | val system = ActorSystem("Hotswap-Become") 48 | 49 | val userStorage = system.actorOf(Props[UserStorage], "userStorage") 50 | 51 | userStorage ! Operation(DBOperation.Create, Some(User("Admin", "admin@packt.com"))) 52 | userStorage ! Connect 53 | userStorage ! Disconnect 54 | 55 | Thread.sleep(100) 56 | 57 | system.terminate() 58 | } -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/sharding/Frontend.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.sharding 2 | 3 | import akka.actor.{Actor, ActorLogging, ActorRef} 4 | import akka.cluster.sharding.ClusterSharding 5 | import scala.concurrent.duration._ 6 | import scala.util.Random 7 | 8 | /** Created by luhtonen on 13/04/16. */ 9 | class Frontend extends Actor with ActorLogging { 10 | import Frontend._ 11 | import context.dispatcher 12 | 13 | val counterRegion: ActorRef = ClusterSharding(context.system).shardRegion(Counter.shardName) 14 | 15 | // Every 3 second will send increment operation 16 | context.system.scheduler.schedule(3.seconds, 3.seconds, self, Tick(Inc)) 17 | 18 | // Every 12 second will send Decrement operation 19 | context.system.scheduler.schedule(6.seconds, 6.seconds, self, Tick(Dec)) 20 | 21 | // Every 30 second will send get operation 22 | context.system.scheduler.schedule(10.seconds, 10.seconds, self, Tick(Get)) 23 | 24 | def receive = { 25 | case Tick(Inc) => 26 | log.info(s"Frontend: Send Increment message to shard region.") 27 | counterRegion ! Counter.CounterMessage(getId, Counter.Increment) 28 | case Tick(Dec) => 29 | log.info(s"Frontend: Send Decrement message to shard region.") 30 | counterRegion ! Counter.CounterMessage(getId, Counter.Decrement) 31 | case Tick(Get) => 32 | log.info(s"Frontend: Send Get message to shard region.") 33 | counterRegion ! Counter.CounterMessage(getId, Counter.Get) 34 | } 35 | 36 | def getId = Random.nextInt(4) 37 | } 38 | 39 | object Frontend { 40 | sealed trait Op 41 | case object Inc extends Op 42 | case object Dec extends Op 43 | case object Get extends Op 44 | 45 | case class Tick(op: Op) 46 | } -------------------------------------------------------------------------------- /akka-http-client-side-api/src/main/scala/org/elu/akka/ConnectionLevel.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import akka.http.scaladsl.Http 5 | import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ 6 | import akka.http.scaladsl.model.StatusCodes._ 7 | import akka.http.scaladsl.model.{HttpRequest, HttpResponse} 8 | import akka.http.scaladsl.unmarshalling.Unmarshal 9 | import akka.stream.ActorMaterializer 10 | import akka.stream.scaladsl.{Flow, Sink, Source} 11 | 12 | import scala.concurrent.ExecutionContext.Implicits.global 13 | import scala.concurrent.Future 14 | 15 | /** Created by luhtonen on 21/04/16. */ 16 | object ConnectionLevel extends App { 17 | import JsonProtocol._ 18 | implicit val system = ActorSystem() 19 | implicit val materializer = ActorMaterializer() 20 | 21 | val connectionFlow: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] = 22 | Http().outgoingConnection("api.ipify.org") 23 | 24 | val responseFuture = Source.single(HttpRequest(uri = "/?format=json")) 25 | .via(connectionFlow) 26 | .runWith(Sink.head) 27 | 28 | responseFuture map { res => 29 | res.status match { 30 | case OK => 31 | Unmarshal(res.entity).to[IpInfo].map { info => 32 | println(s"The information for my ip is: $info") 33 | shutdown() 34 | } 35 | case _ => 36 | Unmarshal(res.entity).to[String].map { body => 37 | println(s"The response status is ${res.status} and response body is $body") 38 | shutdown() 39 | } 40 | } 41 | } 42 | 43 | def shutdown() = { 44 | Http().shutdownAllConnectionPools().onComplete { _ => 45 | system.terminate() 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /hotswap-behavior/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 3 : Working with Akka Basic Tools 2 | ## Replacing Actor Behavior via become/unbecome 3 | This example demonstrates changing the state of the actor with `become()` and `unbecome()` functions. If using simple Actors without any extra traits, then it is very important to remember that order of message sending is very important. 4 | 5 | For example if class UserStorage is defined as following: 6 | 7 | class UserStorage extends Actor 8 | 9 | Then all messages sent when `UserStorage` is in state `disconnected` will be lost. For example in this code: 10 | 11 | userStorage ! Operation(DBOperation.Create, Some(User("Admin", "admin@packt.com"))) 12 | userStorage ! Connect 13 | userStorage ! Disconnect 14 | 15 | `DBOperation.Create` will not be processed, because `UserStorage` actor is in `disconnected` state at the time this message is sent and this message will be just discarded. 16 | 17 | One of the ways to fix this problem, is to change the order of the messages sending like following: 18 | 19 | userStorage ! Connect 20 | userStorage ! Operation(DBOperation.Create, Some(User("Admin", "admin@packt.com"))) 21 | userStorage ! Disconnect 22 | 23 | The other way is to use Stash trait like this: 24 | 25 | class UserStorage extends Actor with Stash 26 | 27 | This trait enables message stashing to be processed later. Following code in disconnected state will stash all messages, not handled by other cases: 28 | 29 | case _ => 30 | stash() 31 | 32 | And when `Connect` message will be received messages can be unstashed by this function call: 33 | 34 | unstashAll() 35 | 36 | ## Replacing Actor Behavior via FSM 37 | This is very interesting example of Finite State Machine (FSM) usage along with Akka Actors. -------------------------------------------------------------------------------- /stream-io/src/main/scala/org/elu/akka/WriteStream.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import java.io.File 4 | 5 | import akka.actor.ActorSystem 6 | import akka.stream.scaladsl._ 7 | import akka.stream.{ActorMaterializer, ClosedShape} 8 | import akka.util.ByteString 9 | 10 | import scala.concurrent.ExecutionContext.Implicits.global 11 | import scala.util.{Failure, Success} 12 | 13 | /** Created by luhtonen on 21/04/16. */ 14 | object WriteStream extends App { 15 | implicit val actorSystem = ActorSystem() 16 | implicit val materializer = ActorMaterializer() 17 | 18 | // Source 19 | val source = Source(1 to 10000).filter(isPrime) 20 | // Sink 21 | val sink = FileIO.toFile(new File("target/prime.txt")) 22 | // file output sink 23 | val fileSink = Flow[Int] 24 | .map(i => ByteString(i.toString)) 25 | .toMat(sink)(Keep.right) 26 | // console output sink 27 | val consoleSink = Sink.foreach[Int](println) 28 | 29 | // send primes to both file sink and console sink using graph API 30 | val g = RunnableGraph.fromGraph(GraphDSL.create(fileSink, consoleSink)(Keep.left) { implicit builder => 31 | (file, console) => 32 | import GraphDSL.Implicits._ 33 | 34 | val broadCast = builder.add(Broadcast[Int](2)) 35 | 36 | source ~> broadCast ~> file 37 | broadCast ~> console 38 | 39 | ClosedShape 40 | }).run() 41 | 42 | // ensure the output file is closed and the system shutdown upon completion 43 | g.onComplete { 44 | case Success(_) => 45 | actorSystem.terminate() 46 | case Failure(e) => 47 | println(s"Failure: ${e.getMessage}") 48 | actorSystem.terminate() 49 | } 50 | 51 | def isPrime(n: Int): Boolean = { 52 | if (n <= 1) false 53 | else if (n == 2) true 54 | else !(2 until n).exists(x => n % x == 0) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /stream-test/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 7: Working with Akka Streams 2 | ## Testing Streams 3 | Here's examples on how to test streams with different techniques. 4 | 5 | ### Dependencies 6 | Akka Streams have `TestKit` test framework for testing it. Here's list of dependencies you will need for testing Akka Streams: 7 | 8 | libraryDependencies ++= Seq( 9 | "com.typesafe.akka" %% "akka-actor" % "2.4.4", 10 | "com.typesafe.akka" %% "akka-stream" % "2.4.4", 11 | "com.typesafe.akka" %% "akka-stream-testkit" % "2.4.4", 12 | "org.scalatest" %% "scalatest" % "2.2.6" % "test", 13 | "com.typesafe.akka" %% "akka-testkit" % "2.4.4" % "test" 14 | ) 15 | 16 | ### Migration from experimental version 1.x to 2.4.x 17 | In class `StreamActorSpec` have to change old code. Following `apply()` function was removed from `Source` object of Akka Stream: 18 | 19 | val source = Source(0.millis, 200.millis, Tick) 20 | 21 | It was replaced by `tick()` function: 22 | 23 | val source = Source.tick(0.millis, 200.millis, Tick) 24 | 25 | In class `StreamKitSpec` following call with empty parameter list was not working: 26 | 27 | sourceUnderTest.runWith(TestSink.probe[Int]()) 28 | 29 | `ActorSystem` argument should be passed explicitly: 30 | 31 | sourceUnderTest.runWith(TestSink.probe[Int](system)) 32 | 33 | In same class following code is causing test failure: 34 | 35 | sourceUnderTest.runWith(TestSink.probe[Int]()) 36 | .request(2) 37 | .expectNext(2, 4) 38 | .expectComplete() 39 | 40 | Test fails with the following failure message: 41 | 42 | java.lang.AssertionError: assertion failed: timeout (3 seconds) during expectMsg while waiting for OnComplete 43 | 44 | It looks like `stream testkit` have a bug which causes `expectComplete()` to fail after `extectNext()` call. Here's the link to reported [issue](https://github.com/akka/akka/issues/19326). -------------------------------------------------------------------------------- /rest-api/src/main/scala/org/elu/akka/models/TweetEntity.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.models 2 | 3 | import reactivemongo.bson.{BSONDocumentReader, BSONDocumentWriter, BSONDocument, BSONObjectID} 4 | import spray.json._ 5 | 6 | import scala.util.Success 7 | 8 | case class TweetEntity(id: BSONObjectID = BSONObjectID.generate, 9 | author: String, 10 | body: String) 11 | 12 | object TweetEntity { 13 | implicit def toTweetEntity(tweet: Tweet) = TweetEntity(author = tweet.author, body = tweet.body) 14 | 15 | implicit object TweetEntityBSONReader extends BSONDocumentReader[TweetEntity] { 16 | 17 | def read(doc: BSONDocument): TweetEntity = 18 | TweetEntity( 19 | id = doc.getAs[BSONObjectID]("_id").get, 20 | author = doc.getAs[String]("author").get, 21 | body = doc.getAs[String]("body").get 22 | ) 23 | } 24 | 25 | implicit object TweetEntityBSONWriter extends BSONDocumentWriter[TweetEntity] { 26 | def write(tweetEntity: TweetEntity): BSONDocument = 27 | BSONDocument( 28 | "_id" -> tweetEntity.id, 29 | "author" -> tweetEntity.author, 30 | "body" -> tweetEntity.body 31 | ) 32 | } 33 | } 34 | 35 | object TweetEntityProtocol extends DefaultJsonProtocol { 36 | 37 | implicit object BSONObjectIdProtocol extends RootJsonFormat[BSONObjectID] { 38 | override def write(obj: BSONObjectID): JsValue = JsString(obj.stringify) 39 | override def read(json: JsValue): BSONObjectID = json match { 40 | case JsString(id) => BSONObjectID.parse(id) match { 41 | case Success(validId) => validId 42 | case _ => deserializationError("Invalid BSON Object Id") 43 | } 44 | case _ => deserializationError("BSON Object Id expected") 45 | } 46 | } 47 | 48 | implicit val EntityFormat = jsonFormat3(TweetEntity.apply) 49 | } 50 | -------------------------------------------------------------------------------- /akka-http-client-side-api/src/main/scala/org/elu/akka/HostLevel.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import akka.http.scaladsl.Http 5 | import akka.http.scaladsl.model.{HttpRequest, HttpResponse} 6 | import akka.http.scaladsl.model.StatusCodes._ 7 | import akka.http.scaladsl.unmarshalling.Unmarshal 8 | import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ 9 | import akka.stream.ActorMaterializer 10 | import akka.stream.scaladsl.{Sink, Source} 11 | 12 | import scala.concurrent.Future 13 | import scala.concurrent.ExecutionContext.Implicits.global 14 | import scala.util.{Failure, Success, Try} 15 | 16 | /** Created by luhtonen on 21/04/16. */ 17 | object HostLevel extends App { 18 | import JsonProtocol._ 19 | 20 | implicit val system = ActorSystem() 21 | implicit val materializer = ActorMaterializer() 22 | 23 | val poolClientFlow = Http().cachedHostConnectionPool[Int]("api.ipify.org") 24 | 25 | val responseFuture: Future[(Try[HttpResponse], Int)] = 26 | Source.single(HttpRequest(uri = "/?format=json") -> 4) 27 | .via(poolClientFlow) 28 | .runWith(Sink.head) 29 | 30 | responseFuture map { 31 | case (Success(res), _) => 32 | res.status match { 33 | case OK => 34 | Unmarshal(res.entity).to[IpInfo].map { info => 35 | println(s"The information for my ip is: $info") 36 | shutdown() 37 | } 38 | case _ => 39 | Unmarshal(res.entity).to[String].map { body => 40 | println(s"The response status is ${res.status} and response body is $body") 41 | shutdown() 42 | } 43 | } 44 | case (Failure(err), i) => 45 | println(s"Error Happened $err") 46 | shutdown() 47 | } 48 | 49 | def shutdown() = { 50 | Http().shutdownAllConnectionPools().onComplete { _ => 51 | system.terminate() 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /testing-actors/src/test/scala/org/elu/akka/CounterSpec.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Props, ActorSystem} 4 | import akka.testkit.{ImplicitSender, TestProbe, TestKit} 5 | import org.scalatest.{MustMatchers, BeforeAndAfterAll, FlatSpecLike} 6 | import scala.concurrent.duration._ 7 | 8 | /** Created by luhtonen on 17/04/16. */ 9 | class CounterSpec extends TestKit(ActorSystem("test-system")) 10 | with FlatSpecLike 11 | with ImplicitSender 12 | with BeforeAndAfterAll 13 | with MustMatchers { 14 | 15 | override def afterAll = { 16 | TestKit.shutdownActorSystem(system) 17 | } 18 | 19 | "Counter Actor" should "handle GetCount message with using TestProbe" in { 20 | val sender = TestProbe() 21 | 22 | val counter = system.actorOf(Props[Counter]) 23 | 24 | sender.send(counter, Counter.Increment) 25 | sender.send(counter, Counter.GetCount) 26 | 27 | val state = sender.expectMsgType[Int] 28 | state must equal(1) 29 | } 30 | 31 | it should "handle Increment message" in { 32 | val counter = system.actorOf(Props[Counter]) 33 | 34 | counter ! Counter.Increment 35 | 36 | expectNoMsg(1.second) 37 | } 38 | 39 | it should "handle Decrement message" in { 40 | val counter = system.actorOf(Props[Counter]) 41 | 42 | counter ! Counter.Decrement 43 | 44 | expectNoMsg(1.seconds) 45 | } 46 | 47 | it should "handle GetCount message" in { 48 | val counter = system.actorOf(Props[Counter]) 49 | 50 | counter ! Counter.GetCount 51 | 52 | expectMsg(0) 53 | } 54 | 55 | it should "handle sequence of messages" in { 56 | val counter = system.actorOf(Props[Counter]) 57 | 58 | counter ! Counter.Increment 59 | counter ! Counter.Increment 60 | 61 | counter ! Counter.Decrement 62 | 63 | counter ! Counter.Increment 64 | counter ! Counter.GetCount 65 | 66 | expectMsg(2) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /graph-flows/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 7: Working with Akka Streams 2 | ## Working with Graphs 3 | This simple example shows how to user Akka Streams Graph. 4 | 5 | ### Migration from experimental 1.0 to 2.4.x 6 | Akka Streams is now in release stage and there are significant changes from `experimental` version `1.0` to version `2.4.x`. 7 | 8 | #### FlowGraph is removed 9 | `FlowGraph` is removed from version 2.x and replaced with several other objects. 10 | 11 | Official [Migration Guide](http://doc.akka.io/docs/akka-stream-and-http-experimental/2.0.2/scala/migration-guide-1.0-2.x-scala.html#FlowGraph_class_and_builder_methods_have_been_renamed) describes **Update procedure**: 12 | > 1. Search and replace all occurrences of `FlowGraph` with `GraphDSL`. 13 | 2. Replace all occurrences of `GraphDSL.partial()` or `GraphDSL.closed()` with `GraphDSL.create()`. 14 | 3. Add `ClosedShape` as a return value of the builder block if it was `FlowGraph.closed()` before. 15 | 4. Wrap the closed graph with `RunnableGraph.fromGraph` if it was `FlowGraph.closed()` before. 16 | 17 | The following code from the old experimental version: 18 | 19 | val g = FlowGraph.closed() { implicit builder: FlowGraph.Builder[Unit] => 20 | import FlowGraph.Implicits._ 21 | 22 | // snipped code here 23 | } 24 | 25 | Should be replaced with the following code: 26 | 27 | val g = RunnableGraph.fromGraph(GraphDSL.create() { implicit builder: GraphDSL.Builder[NotUsed] => 28 | import GraphDSL.Implicits._ 29 | 30 | // snipped code 31 | 32 | ClosedShape 33 | }) 34 | 35 | Old graph builder code `FlowGraph.Builder[Unit]` is in new version replaced with `GraphDSL.Builder[NotUsed]` code. 36 | 37 | Old implicits import `import FlowGraph.Implicits._` is replaced with new `import GraphDSL.Implicits._`. 38 | 39 | Old runnable closed flow graph creation function `FlowGraph.closed()` is replaced with factory function `RunnableGraph.fromGraph()` and explicit returned shape at the end of the creation function `ClosedShape`. -------------------------------------------------------------------------------- /test-fsm/src/main/scala/org/elu/akka/UserStorageFSM.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{ActorSystem, Props, FSM, Stash} 4 | 5 | /** Created by luhtonen on 18/04/16. */ 6 | class UserStorageFSM extends FSM[UserStorageFSM.State, UserStorageFSM.Data] with Stash { 7 | import UserStorageFSM._ 8 | 9 | startWith(Disconnected, EmptyData) 10 | 11 | when(Disconnected) { 12 | case Event(Connect, _) => 13 | println("UserStorage connected to DB") 14 | unstashAll() 15 | goto(Connected) using EmptyData 16 | 17 | case Event(msg, _) => 18 | stash() 19 | stay using EmptyData 20 | } 21 | 22 | when(Connected) { 23 | case Event(Disconnect, _) => 24 | println("UserStorage disconnected from DB") 25 | goto(Disconnected) using EmptyData 26 | 27 | case Event(Operation(op, user), _) => 28 | println(s"UserStorage receive ${op} operation to do in user: ${user}") 29 | stay using EmptyData 30 | } 31 | 32 | initialize() 33 | } 34 | 35 | object UserStorageFSM { 36 | sealed trait State 37 | case object Connected extends State 38 | case object Disconnected extends State 39 | 40 | sealed trait Data 41 | case object EmptyData extends Data 42 | 43 | trait DBOperation 44 | object DBOperation{ 45 | case object Create extends DBOperation 46 | case object Update extends DBOperation 47 | case object Read extends DBOperation 48 | case object Delete extends DBOperation 49 | } 50 | 51 | case object Connect 52 | case object Disconnect 53 | case class Operation(op: DBOperation, user: User) 54 | 55 | case class User(username: String, email: String) 56 | } 57 | 58 | object FiniteStateMachine extends App { 59 | import UserStorageFSM._ 60 | 61 | val system = ActorSystem("Hotswap-FSM") 62 | val userStorage = system.actorOf(Props[UserStorageFSM], "userStorage-fsm") 63 | 64 | userStorage ! Connect 65 | 66 | userStorage ! Operation(DBOperation.Create, User("Admin", "admin@packt.com")) 67 | 68 | userStorage ! Disconnect 69 | 70 | Thread.sleep(100) 71 | system.terminate() 72 | } -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/singleton/Master.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.singleton 2 | 3 | import akka.actor.{ActorLogging, ActorRef} 4 | import akka.persistence.{SnapshotOffer, PersistentActor} 5 | 6 | object Master { 7 | trait Command 8 | case class RegisterWorker(worker: ActorRef) extends Command 9 | case class RequestWork(requester: ActorRef) extends Command 10 | case class Work(requester: ActorRef, op: String) extends Command 11 | 12 | trait Event 13 | case class AddWorker(worker: ActorRef) extends Event 14 | case class AddWork(work: Work) extends Event 15 | case class UpdateWorks(works: List[Work]) extends Event 16 | 17 | case class State(workers: Set[ActorRef], works: List[Work]) 18 | 19 | case object NoWork 20 | } 21 | 22 | class Master extends PersistentActor with ActorLogging { 23 | import Master._ 24 | 25 | var workers: Set[ActorRef] = Set.empty 26 | var works: List[Work] = List.empty 27 | 28 | override def persistenceId: String = self.path.parent.name + "-" + self.path.name 29 | 30 | def updateState(evt: Event): Unit = evt match { 31 | case AddWorker(w) => 32 | workers = workers + w 33 | case AddWork(w) => 34 | works = works :+ w 35 | case UpdateWorks(ws) => 36 | works = ws 37 | } 38 | 39 | val receiveRecover: Receive = { 40 | case evt: Event => 41 | updateState(evt) 42 | case SnapshotOffer(_, snapshot: State) => 43 | workers = snapshot.workers 44 | works = snapshot.works 45 | } 46 | 47 | val receiveCommand: Receive = { 48 | case RegisterWorker(worker) => 49 | persist(AddWorker(worker)) { evt => 50 | updateState(evt) 51 | } 52 | 53 | case RequestWork if works.isEmpty => 54 | sender() ! NoWork 55 | 56 | case RequestWork(requester) if workers.contains(requester) && works.nonEmpty => 57 | sender() ! works.head 58 | persist(UpdateWorks(works.tail)) { evt => 59 | updateState(evt) 60 | } 61 | 62 | case w: Work => 63 | persist(AddWork(w)) { evt => 64 | updateState(evt) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test-fsm/src/test/scala/org/elu/akka/UserStorageSpec.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import akka.testkit.{TestFSMRef, ImplicitSender, TestKit} 5 | import org.scalatest.{FlatSpecLike, BeforeAndAfterAll, MustMatchers} 6 | 7 | /** Created by luhtonen on 18/04/16. */ 8 | class UserStorageSpec extends TestKit(ActorSystem("test-system")) 9 | with ImplicitSender 10 | with FlatSpecLike 11 | with BeforeAndAfterAll 12 | with MustMatchers { 13 | 14 | import UserStorageFSM._ 15 | 16 | override def afterAll { 17 | TestKit.shutdownActorSystem(system) 18 | } 19 | 20 | "User Storage" should "Start with disconnected state and empty data" in { 21 | val storage = TestFSMRef(new UserStorageFSM()) 22 | 23 | storage.stateName must equal(Disconnected) 24 | storage.stateData must equal(EmptyData) 25 | } 26 | 27 | it should "be connected state if it receive a connect message" in { 28 | val storage = TestFSMRef(new UserStorageFSM()) 29 | 30 | storage ! Connect 31 | 32 | storage.stateName must equal(Connected) 33 | storage.stateData must equal(EmptyData) 34 | } 35 | 36 | it should "be still in disconnected state if it receive any other messages" in { 37 | val storage = TestFSMRef(new UserStorageFSM()) 38 | 39 | storage ! DBOperation.Create 40 | 41 | storage.stateName must equal(Disconnected) 42 | storage.stateData must equal(EmptyData) 43 | } 44 | 45 | it should "be switch to disconnected when it receive a disconnect message on Connected state" in { 46 | val storage = TestFSMRef(new UserStorageFSM()) 47 | 48 | storage ! Connect 49 | storage ! Disconnect 50 | 51 | storage.stateName must equal(Disconnected) 52 | storage.stateData must equal(EmptyData) 53 | } 54 | 55 | it should "be still on connected state if it receive any DB operations" in { 56 | val storage = TestFSMRef(new UserStorageFSM()) 57 | 58 | storage ! Connect 59 | storage ! DBOperation.Create 60 | 61 | storage.stateName must equal(Connected) 62 | storage.stateData must equal(EmptyData) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /hello-akka/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 1: Exploring the Actor World 2 | This is brief introduction to Akka and Actor model with traditional `hello-world` example project. 3 | 4 | ## Building Akka project 5 | In this video serie all Akka projects are developed using [Scala programming language](http://www.scala-lang.org/). There are several build tools used for building Scala projects: [Maven](https://maven.apache.org/), [Gradle](http://gradle.org/) and [SBT](http://www.scala-sbt.org/). SBT is the mostly used tool for Scala projects and in the following examples this tool is used to build all example projects. 6 | 7 | Basic SBT build settings go in a file called `build.sbt`, located in the project’s base directory. All project will have similar configuration defined in this file: 8 | 9 | name := "hello-akka" 10 | 11 | version := "1.0" 12 | 13 | scalaVersion := "2.11.8" 14 | 15 | libraryDependencies ++= Seq( 16 | "com.typesafe.akka" %% "akka-actor" % "2.4.0" 17 | ) 18 | 19 | * `name` defines the name of the project. This should be unique for every project. 20 | * `version` defines the project version. In our example it is not important and it have same value in all projects. 21 | * `scalaVersion` defines the version of scala to be used to build the project. This also will be the same for all projects. 22 | * `libraryDependencies` is a list of project dependendies. Since this serie is talking about Akka all projects will require at least one dependency: `"com.typesafe.akka" %% "akka-actor" % "2.4.0"`. Which says that it requires `com.typesafe.akka` package and `akka-actor` artifact in this package of version `2.4.0`. `%%` between package and artifact tells SBT to automatically search for artifact for defined Scala version. In this example it will resolve it to `akka-actor_2.11`. 23 | 24 | Some of the projects will require extra configurations and additional dependencies. Those will be descussed in those particular projects. 25 | 26 | To run project execute following command in the project root directory: 27 | 28 | sbt run 29 | 30 | This will compile and run the project. For more information about building read SBT documentation. 31 | 32 | -------------------------------------------------------------------------------- /ordered-termination/src/main/scala/org/elu/akka/pattern/Terminator.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.pattern 2 | 3 | import akka.actor.{Terminated, Props, ActorRef, Actor} 4 | import akka.pattern.{gracefulStop, pipe} 5 | import scala.concurrent.Future 6 | import scala.concurrent.duration._ 7 | 8 | object Terminator { 9 | // Protocol between us and the master 10 | case class GetChildren(forActor: ActorRef) 11 | case class Children(kids: Iterable[ActorRef]) 12 | } 13 | 14 | class Terminator(childProps: Props, numOfChildren: Int) extends Actor { 15 | import Terminator._ 16 | import context._ 17 | 18 | implicit val stopTimeout = 5.minutes 19 | case object AllDead 20 | 21 | override def preStart() { 22 | (1 to numOfChildren).foreach { i => 23 | context.actorOf(childProps, s"worker-$i") 24 | } 25 | } 26 | 27 | // Derivations implement 28 | def order(kids: Iterable[ActorRef]): Iterable[ActorRef] = { 29 | kids.toSeq.reverse 30 | } 31 | 32 | // Kills kids in the order given by the list 33 | def killKids(kids: List[ActorRef]): Future[Any] = { 34 | kids match { 35 | case kid :: Nil => 36 | gracefulStop(kid, stopTimeout).flatMap { _ => 37 | Future { AllDead } 38 | } 39 | case Nil => 40 | Future { AllDead } 41 | case kid :: rest => 42 | gracefulStop(kid, stopTimeout).flatMap { _ => 43 | killKids(rest) 44 | } 45 | } 46 | } 47 | 48 | // Initially we're waiting for the request for kids 49 | def waiting: Receive = { 50 | case GetChildren(forActor) => 51 | watch(forActor) 52 | forActor ! Children(children) 53 | become(childrenGiven(forActor)) 54 | } 55 | 56 | // Once we've done it, we want to lock our aspect 57 | // to the guy we gave them to 58 | def childrenGiven(to: ActorRef): Receive = { 59 | case GetChildren(forActor) if sender == to => 60 | forActor ! Children(children) 61 | case Terminated(`to`) => 62 | killKids(order(children).toList) pipeTo self 63 | case AllDead => 64 | stop(self) 65 | } 66 | 67 | // Start waiting 68 | def receive = waiting 69 | } 70 | -------------------------------------------------------------------------------- /hotswap-behavior/src/main/scala/org/elu/akka/UserStorageFSM.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{ActorSystem, Props, FSM, Stash} 4 | 5 | object UserStorageFSM { 6 | 7 | // FSM State 8 | sealed trait State 9 | case object Connected extends State 10 | case object Disconnected extends State 11 | 12 | // FSM Data 13 | sealed trait Data 14 | case object EmptyData extends Data 15 | 16 | 17 | trait DBOperation 18 | object DBOperation{ 19 | case object Create extends DBOperation 20 | case object Update extends DBOperation 21 | case object Read extends DBOperation 22 | case object Delete extends DBOperation 23 | } 24 | 25 | case object Connect 26 | case object Disconnect 27 | case class Operation(op: DBOperation, user: User) 28 | 29 | case class User(username: String, email: String) 30 | } 31 | 32 | class UserStorageFSM extends FSM[UserStorageFSM.State, UserStorageFSM.Data] with Stash { 33 | import UserStorageFSM._ 34 | 35 | // 1. define start with 36 | startWith(Disconnected, EmptyData) 37 | 38 | // 2. define states 39 | when(Disconnected){ 40 | case Event(Connect, _) => 41 | println("UserStorage Connected to DB") 42 | unstashAll() 43 | goto(Connected) using(EmptyData) 44 | case Event(_, _) => 45 | stash() 46 | stay using(EmptyData) 47 | } 48 | 49 | when(Connected) { 50 | case Event(Disconnect, _) => 51 | println("UserStorage disconnected from DB") 52 | goto(Disconnected) using EmptyData 53 | 54 | case Event(Operation(op, user), _) => 55 | println(s"UserStorage receive ${op} operation to do in user: ${user}") 56 | stay using EmptyData 57 | } 58 | 59 | // 3. initialize 60 | initialize() 61 | } 62 | 63 | object FiniteStateMachine extends App { 64 | import UserStorageFSM._ 65 | 66 | val system = ActorSystem("Hotswap-FSM") 67 | val userStorage = system.actorOf(Props[UserStorageFSM], "userStorage-fsm") 68 | 69 | userStorage ! Connect 70 | userStorage ! Operation(DBOperation.Create, User("Admin", "admin@packt.com")) 71 | userStorage ! Disconnect 72 | 73 | Thread.sleep(100) 74 | system.terminate() 75 | } -------------------------------------------------------------------------------- /stream-test/src/test/scala/org/elu/akka/StreamActorSpec.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import akka.pattern.pipe 5 | import akka.stream.{OverflowStrategy, ActorMaterializer} 6 | import akka.stream.scaladsl._ 7 | import akka.testkit.{ImplicitSender, TestKit, TestProbe} 8 | import org.scalatest.{BeforeAndAfterAll, FlatSpecLike, MustMatchers} 9 | 10 | import scala.concurrent.Await 11 | import scala.concurrent.ExecutionContext.Implicits.global 12 | import scala.concurrent.duration._ 13 | 14 | /** Created by luhtonen on 18/04/16. */ 15 | class StreamActorSpec extends TestKit(ActorSystem("test-system")) 16 | with ImplicitSender 17 | with FlatSpecLike 18 | with BeforeAndAfterAll 19 | with MustMatchers { 20 | 21 | override def afterAll { 22 | TestKit.shutdownActorSystem(system) 23 | } 24 | 25 | implicit val flowMaterializer = ActorMaterializer() 26 | 27 | "With TestKit" should "test actor receive elements from the sink" in { 28 | val source = Source(1 to 4).grouped(2) 29 | val testProbe = TestProbe() 30 | 31 | source.runWith(Sink.head) pipeTo testProbe.ref 32 | 33 | testProbe.expectMsg(Seq(1, 2)) 34 | } 35 | 36 | it should "have a control over a receiving elements" in { 37 | case object Tick 38 | 39 | val source = Source.tick(0.millis, 200.millis, Tick) 40 | val probe = TestProbe() 41 | val sink = Sink.actorRef(probe.ref, "completed") 42 | val runnable = source.to(sink).run() 43 | 44 | probe.expectMsg(1.second, Tick) 45 | probe.expectNoMsg(100.millis) 46 | probe.expectMsg(200.millis, Tick) 47 | runnable.cancel() 48 | probe.expectMsg(200.millis, "completed") 49 | } 50 | 51 | it should "have a control over elements to be sent" in { 52 | val sink = Flow[Int].map(_.toString).toMat(Sink.fold("")(_ + _))(Keep.right) 53 | val source = Source.actorRef(8, overflowStrategy = OverflowStrategy.fail) 54 | val (ref, result) = source.toMat(sink)(Keep.both).run() 55 | 56 | ref ! 1 57 | ref ! 2 58 | ref ! 3 59 | ref ! akka.actor.Status.Success("done") 60 | 61 | Await.result(result, 200.millis) must equal("123") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/sharding/Counter.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.sharding 2 | 3 | import scala.concurrent.duration._ 4 | import akka.actor.{Props, ActorLogging} 5 | import akka.cluster.sharding.ShardRegion 6 | import akka.persistence.PersistentActor 7 | 8 | /** Created by luhtonen on 13/04/16. */ 9 | class Counter extends PersistentActor with ActorLogging { 10 | import Counter._ 11 | 12 | context.setReceiveTimeout(120.seconds) 13 | 14 | override def persistenceId: String = self.path.parent.name + "-" + self.path.name 15 | 16 | var count = 0 17 | 18 | def updateState(event: CounterChanged): Unit = 19 | count += event.delta 20 | 21 | override def receiveRecover: Receive = { 22 | case evt: CounterChanged ⇒ updateState(evt) 23 | } 24 | 25 | override def receiveCommand: Receive = { 26 | case Increment => 27 | log.info(s"Counter with path: ${self} recevied Increment Command") 28 | persist(CounterChanged(+1))(updateState) 29 | case Decrement => 30 | log.info(s"Counter with path: ${self} recevied Decrement Command") 31 | persist(CounterChanged(-1))(updateState) 32 | case Get => 33 | log.info(s"Counter with path: ${self} recevied Get Command") 34 | log.info(s"Count = ${count}") 35 | sender() ! count 36 | case Stop => 37 | context.stop(self) 38 | } 39 | } 40 | 41 | object Counter { 42 | trait Command 43 | case object Increment extends Command 44 | case object Decrement extends Command 45 | case object Get extends Command 46 | case object Stop extends Command 47 | 48 | trait Event 49 | case class CounterChanged(delta: Int) extends Event 50 | 51 | // Sharding Name 52 | val shardName: String = "Counter" 53 | 54 | // outside world if he want to send message to sharding should use this message 55 | case class CounterMessage(id: Long, cmd: Command) 56 | 57 | // id extrator 58 | val idExtractor: ShardRegion.ExtractEntityId = { 59 | case CounterMessage(id, msg) => (id.toString, msg) 60 | } 61 | 62 | // shard resolver 63 | val shardResolver: ShardRegion.ExtractShardId = { 64 | case CounterMessage(id, msg) => (id % 12).toString 65 | } 66 | 67 | def props() = Props[Counter] 68 | } 69 | -------------------------------------------------------------------------------- /persistence/src/main/scala/org/elu/akka/Counter.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorLogging 4 | import akka.persistence._ 5 | 6 | object Counter { 7 | sealed trait Operation { 8 | val count: Int 9 | } 10 | 11 | case class Increment(override val count: Int) extends Operation 12 | case class Decrement(override val count: Int) extends Operation 13 | 14 | case class Cmd(op: Operation) 15 | case class Evt(op: Operation) 16 | 17 | case class State(count: Int) 18 | } 19 | 20 | class Counter extends PersistentActor with ActorLogging { 21 | import Counter._ 22 | 23 | println("Starting ........................") 24 | 25 | // Persistent Identifier 26 | override def persistenceId = "counter-example" 27 | 28 | var state: State = State(count = 0) 29 | 30 | def updateState(evt: Evt): Unit = evt match { 31 | case Evt(Increment(count)) => 32 | state = State(count = state.count + count) 33 | takeSnapshot 34 | case Evt(Decrement(count)) => 35 | state = State(count = state.count - count) 36 | takeSnapshot 37 | } 38 | 39 | // Persistent receive on recovery mood 40 | val receiveRecover: Receive = { 41 | case evt: Evt => 42 | println(s"Counter receive ${evt} on recovering mood") 43 | updateState(evt) 44 | case SnapshotOffer(_, snapshot: State) => 45 | println(s"Counter receive snapshot with data: ${snapshot} on recovering mood") 46 | state = snapshot 47 | case RecoveryCompleted => 48 | println(s"Recovery Complete and Now I'll swtich to receiving mode :)") 49 | } 50 | 51 | // Persistent receive on normal mood 52 | val receiveCommand: Receive = { 53 | case cmd @ Cmd(op) => 54 | println(s"Counter receive ${cmd}") 55 | persist(Evt(op)) { evt => 56 | updateState(evt) 57 | } 58 | case "print" => 59 | println(s"The Current state of counter is ${state}") 60 | case SaveSnapshotSuccess(metadata) => 61 | println(s"save snapshot succeed.") 62 | case SaveSnapshotFailure(metadata, reason) => 63 | println(s"save snapshot failed and failure is ${reason}") 64 | } 65 | 66 | def takeSnapshot = { 67 | if(state.count % 5 == 0){ 68 | saveSnapshot(state) 69 | } 70 | } 71 | 72 | // method to reset recovery 73 | // override def recovery = Recovery.none 74 | } -------------------------------------------------------------------------------- /rest-api/src/main/scala/org/elu/akka/Api.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import akka.http.scaladsl.Http 5 | import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ 6 | import akka.http.scaladsl.model.StatusCodes.{Created, NoContent, OK} 7 | import akka.http.scaladsl.server.Directives._ 8 | import akka.http.scaladsl.server.PathMatchers.Segment 9 | import akka.stream.{ActorMaterializer, Materializer} 10 | import org.elu.akka.db.TweetManager 11 | import org.elu.akka.models.{Tweet, TweetEntity, TweetEntityProtocol, TweetProtocol} 12 | import spray.json._ 13 | 14 | import scala.concurrent.ExecutionContext 15 | import scala.io.StdIn 16 | 17 | trait RestApi { 18 | import TweetEntity._ 19 | import TweetEntityProtocol.EntityFormat 20 | import TweetProtocol._ 21 | 22 | implicit val system: ActorSystem 23 | implicit val materializer: Materializer 24 | implicit val ec: ExecutionContext 25 | 26 | val route = 27 | pathPrefix("tweets") { 28 | (post & entity(as[Tweet])) { tweet => 29 | complete { 30 | TweetManager.save(tweet) map { r => 31 | Created -> Map("id" -> r.id).toJson 32 | } 33 | } 34 | } ~ 35 | (get & path(Segment)) { id => 36 | complete { 37 | TweetManager.findById(id) map { t => 38 | OK -> t 39 | } 40 | } 41 | } ~ 42 | (delete & path(Segment)) { id => 43 | complete { 44 | TweetManager.deleteById(id) map { _ => 45 | NoContent 46 | } 47 | } 48 | } /*~ 49 | TODO: fix get all tweets call 50 | get { 51 | complete { 52 | TweetManager.find map { ts => 53 | OK -> ts.map(_.as[TweetEntity]) 54 | } 55 | } 56 | }*/ 57 | } 58 | } 59 | 60 | object Api extends App with RestApi { 61 | override implicit val system = ActorSystem("rest-api") 62 | override implicit val materializer = ActorMaterializer() 63 | override implicit val ec = system.dispatcher 64 | 65 | val bindingFuture = Http().bindAndHandle(route, "localhost", 8080) 66 | 67 | println(s"Server online at http://localhost:8080/\nPress RETURN to stop...") 68 | StdIn.readLine() 69 | 70 | bindingFuture 71 | .flatMap(_.unbind()) 72 | .onComplete(_ => system.terminate()) 73 | } 74 | -------------------------------------------------------------------------------- /balancing-workload/src/main/scala/org/elu/akka/Worker.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{ActorRef, Actor, ActorLogging, ActorPath} 4 | 5 | /** Created by luhtonen on 27/04/16. */ 6 | abstract class Worker(masterLocation: ActorPath) extends Actor with ActorLogging { 7 | import MasterWorkerProtocol._ 8 | 9 | // We need to know where the master is 10 | val master = context.actorSelection(masterLocation) 11 | 12 | // This is how our derivations will interact with us. It 13 | // allows dervations to complete work asynchronously 14 | case class WorkComplete(result: Any) 15 | 16 | // Required to be implemented 17 | def doWork(workSender: ActorRef, work: Any): Unit 18 | 19 | // Notify the Master that we're alive 20 | override def preStart() = master ! WorkerCreated(self) 21 | 22 | // This is the state we're in when we're working on something. 23 | // In this state we can deal with messages in a much more 24 | // reasonable manner 25 | def working(work: Any): Receive = { 26 | // Pass... we're already working 27 | case WorkIsReady => 28 | // Pass... we're already working 29 | case NoWorkToBeDone => 30 | // Pass... we shouldn't even get this 31 | case WorkToBeDone(_) => 32 | log.error("Yikes. Master told me to do work, while I'm working.") 33 | // Our derivation has completed its task 34 | case WorkComplete(result) => 35 | log.info("Work is complete. Result {}.", result) 36 | master ! WorkIsDone(self) 37 | master ! WorkerRequestsWork(self) 38 | // We're idle now 39 | context.become(idle) 40 | } 41 | 42 | // In this state we have no work to do. There really are only 43 | // two messages that make sense while we're in this state, and 44 | // we deal with them specially here 45 | def idle: Receive = { 46 | // Master says there's work to be done, let's ask for it 47 | case WorkIsReady => 48 | log.info("Requesting work") 49 | master ! WorkerRequestsWork(self) 50 | // Send the work off to the implementation 51 | case WorkToBeDone(work) => 52 | log.info("Got work {}", work) 53 | doWork(sender, work) 54 | context.become(working(work)) 55 | // We asked for it, but either someone else got it first, or 56 | // there's literally no work to be done 57 | case NoWorkToBeDone => 58 | log.info("The master doesn't have any works") 59 | } 60 | 61 | def receive = idle 62 | } 63 | -------------------------------------------------------------------------------- /rest-api/src/test/scala/org/elu/akka/ApiSpec.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ 4 | import akka.http.scaladsl.model.StatusCodes 5 | import akka.http.scaladsl.testkit.ScalatestRouteTest 6 | import org.elu.akka.db.{Created, TweetManager} 7 | import org.elu.akka.models._ 8 | import org.scalatest.{BeforeAndAfterAll, FlatSpec, MustMatchers} 9 | 10 | import scala.concurrent.Await 11 | import scala.concurrent.duration._ 12 | 13 | /** Created by luhtonen on 27/04/16. */ 14 | class ApiSpec extends FlatSpec 15 | with MustMatchers 16 | with ScalatestRouteTest 17 | with BeforeAndAfterAll 18 | with RestApi { 19 | 20 | import TweetEntityProtocol.EntityFormat 21 | import TweetProtocol._ 22 | 23 | override implicit val ec = system.dispatcher 24 | 25 | override def afterAll: Unit = { 26 | TweetManager.collection.drop(failIfNotFound = false) 27 | } 28 | 29 | "The Server" should "return Ok response when get all tweets" in { 30 | val tweet = Tweet("akka", "Hello World") 31 | val f = TweetManager.save(TweetEntity.toTweetEntity(tweet)) 32 | val Created(id) = Await.result(f, 1.second) 33 | 34 | /* 35 | TODO: fix get all REST endpoint 36 | Get("/tweets") ~> route ~> check { 37 | status must equal(StatusCodes.OK) 38 | val res = responseAs[List[TweetEntity]] 39 | res.size must equal(1) 40 | res(0) must equal(TweetEntity(BSONObjectID(id), tweet.author, tweet.body)) 41 | } 42 | */ 43 | } 44 | 45 | it should "return created response when create new tweet" in { 46 | Post("/tweets", Tweet("akka", "Hello World")) ~> route ~> check { 47 | status must equal(StatusCodes.Created) 48 | } 49 | } 50 | 51 | it should "return No Content response when delete a tweet" in { 52 | val tweet = Tweet("akka", "hello world") 53 | val f = TweetManager.save(TweetEntity.toTweetEntity(tweet)) 54 | val Created(id) = Await.result(f, 1.second) 55 | 56 | Delete(s"/tweets/$id") ~> route ~> check { 57 | status must equal(StatusCodes.NoContent) 58 | } 59 | } 60 | 61 | it should "return Ok response when get a tweet" in { 62 | val tweetEntity = TweetEntity.toTweetEntity(Tweet("akka", "hello world")) 63 | val f = TweetManager.save(tweetEntity) 64 | val Created(id) = Await.result(f, 1.second) 65 | 66 | Get(s"/tweets/$id") ~> route ~> check { 67 | status must equal(StatusCodes.OK) 68 | 69 | responseAs[TweetEntity] must equal(tweetEntity) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /akka-cluster/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 5 : Working with Akka Cluster 2 | ## Building a Cluster 3 | Sample project to build a simple actor cluster with one frontend and 3 backend actors. 4 | 5 | ## Adding Load Balancer to a Cluster Node 6 | Sample project with load balancer of the cluster nodes. 7 | 8 | One important note related to execution context. `context.system.scheduler.schedule()` function requires implicit context. If context is not defined then following error will occur: 9 | 10 | Cannot find an implicit ExecutionContext. You might pass 11 | an (implicit ec: ExecutionContext) parameter to your method 12 | or import scala.concurrent.ExecutionContext.Implicits.global. 13 | 14 | This error can be resolved with the following import as suggested in the error message: 15 | 16 | import scala.concurrent.ExecutionContext.Implicits.global 17 | 18 | In our examples `context.dispatcher` is used: 19 | 20 | import context.dispatcher 21 | 22 | For more information about Execution Context can be found from the official Akka documentation: [Dispatchers](http://doc.akka.io/docs/akka/current/scala/dispatchers.html) and [Futures](http://doc.akka.io/docs/akka/current/scala/futures.html). 23 | 24 | ## Creating a Singleton Actor in the Cluster 25 | Sample project with Singleton Actor. 26 | 27 | In the video wasn't mentioned configuration file, which is loaded by `ConfigFactory.load()` method: 28 | 29 | ConfigFactory.load("singleton") 30 | 31 | This code looks for `singleton.conf` file from `src/main/resources` directory. Here's content of this file: 32 | 33 | akka { 34 | loglevel = INFO 35 | 36 | actor { 37 | provider = "akka.cluster.ClusterActorRefProvider" 38 | } 39 | 40 | remote { 41 | log-remote-lifecycle-events = off 42 | netty.tcp { 43 | hostname = "127.0.0.1" 44 | port = 0 45 | } 46 | } 47 | 48 | cluster { 49 | seed-nodes = [ 50 | "akka.tcp://ClusterSystem@127.0.0.1:2551", 51 | "akka.tcp://ClusterSystem@127.0.0.1:2552"] 52 | 53 | auto-down-unreachable-after = 10s 54 | } 55 | 56 | persistence { 57 | journal.plugin = "akka.persistence.journal.leveldb-shared" 58 | journal.leveldb-shared.store { 59 | # DO NOT USE 'native = off' IN PRODUCTION !!! 60 | native = off 61 | dir = "target/shared-journal" 62 | } 63 | snapshot-store.plugin = "akka.persistence.snapshot-store.local" 64 | snapshot-store.local.dir = "target/snapshots" 65 | } 66 | } 67 | 68 | ## Cluster Sharding 69 | Simple example of how to use Cluster Sharding. -------------------------------------------------------------------------------- /reactive-tweets/src/main/scala/org/elu/akka/TwitterClient.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.ActorSystem 4 | import com.typesafe.config.ConfigFactory 5 | import twitter4j.auth.AccessToken 6 | import twitter4j.conf.ConfigurationBuilder 7 | import twitter4j._ 8 | 9 | import scala.collection.JavaConverters._ 10 | 11 | object TwitterConfiguration { 12 | val config = ConfigFactory.load.getConfig("Twitter") 13 | 14 | val apiKey = config.getString("apiKey") 15 | val apiSecret = config.getString("apiSecret") 16 | val accessToken = config.getString("accessToken") 17 | val accessTokenSecret = config.getString("accessTokenSecret") 18 | } 19 | 20 | object TwitterClient { 21 | 22 | def getInstance: Twitter = { 23 | val cb = new ConfigurationBuilder() 24 | cb.setDebugEnabled(true) 25 | .setOAuthConsumerKey(TwitterConfiguration.apiKey) 26 | .setOAuthConsumerSecret(TwitterConfiguration.apiSecret) 27 | .setOAuthAccessToken(TwitterConfiguration.accessToken) 28 | .setOAuthAccessTokenSecret(TwitterConfiguration.accessTokenSecret) 29 | 30 | val tf = new TwitterFactory(cb.build()) 31 | tf.getInstance() 32 | } 33 | 34 | def retrieveTweets(term: String) = { 35 | val query = new Query(term) 36 | query.setCount(100) 37 | getInstance.search(query).getTweets.asScala.iterator 38 | } 39 | } 40 | 41 | class TwitterStreamClient(val actorSystem: ActorSystem) { 42 | val factory = new TwitterStreamFactory(new ConfigurationBuilder().build()) 43 | val twitterStream = factory.getInstance() 44 | 45 | def init = { 46 | twitterStream.setOAuthConsumer(TwitterConfiguration.apiKey, TwitterConfiguration.apiSecret) 47 | twitterStream.setOAuthAccessToken(new AccessToken(TwitterConfiguration.accessToken, TwitterConfiguration.accessTokenSecret)) 48 | twitterStream.addListener(statusListener) 49 | twitterStream.sample 50 | } 51 | 52 | def statusListener = new StatusListener() { 53 | def onStatus(s: Status) { 54 | actorSystem.eventStream.publish(Tweet(Author(s.getUser.getScreenName), s.getText)) 55 | } 56 | 57 | def onDeletionNotice(statusDeletionNotice: StatusDeletionNotice) {} 58 | 59 | def onTrackLimitationNotice(numberOfLimitedStatuses: Int) {} 60 | 61 | def onException(ex: Exception) { 62 | ex.printStackTrace 63 | } 64 | 65 | def onScrubGeo(arg0: Long, arg1: Long) {} 66 | 67 | def onStallWarning(warning: StallWarning) {} 68 | } 69 | 70 | def stop = { 71 | twitterStream.cleanUp 72 | twitterStream.shutdown 73 | } 74 | } -------------------------------------------------------------------------------- /playing-with-actors/src/main/scala/org/elu/akka/Supervision.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.SupervisorStrategy.{Escalate, Stop, Restart, Resume} 4 | import akka.actor._ 5 | import org.elu.akka.Aphrodite.{ResumeException, StopException, RestartException} 6 | 7 | import scala.concurrent.duration._ 8 | import scala.language.postfixOps 9 | 10 | class Aphrodite extends Actor { 11 | 12 | override def preStart() = { 13 | println("Aphrodite preStart hook....") 14 | } 15 | 16 | override def preRestart(reason: Throwable, message: Option[Any]) = { 17 | println("Aphrodite preRestart hook...") 18 | super.preRestart(reason, message) 19 | } 20 | 21 | override def postRestart(reason: Throwable) = { 22 | println("Aphrodite postRestart hook...") 23 | super.postRestart(reason) 24 | } 25 | 26 | override def postStop() = { 27 | println("Aphrodite postStop...") 28 | } 29 | 30 | def receive = { 31 | case "Resume" => 32 | throw ResumeException 33 | case "Stop" => 34 | throw StopException 35 | case "Restart" => 36 | throw RestartException 37 | case _ => 38 | throw new Exception 39 | } 40 | } 41 | 42 | object Aphrodite { 43 | 44 | case object ResumeException extends Exception 45 | 46 | case object StopException extends Exception 47 | 48 | case object RestartException extends Exception 49 | 50 | } 51 | 52 | class Hera extends Actor { 53 | 54 | var childRef: ActorRef = _ 55 | 56 | override val supervisorStrategy = 57 | OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 second) { 58 | case ResumeException => Resume 59 | case RestartException => Restart 60 | case StopException => Stop 61 | case _: Exception => Escalate 62 | } 63 | 64 | override def preStart() = { 65 | // Create Aphrodite Actor 66 | childRef = context.actorOf(Props[Aphrodite], "Aphrodite") 67 | Thread.sleep(100) 68 | } 69 | 70 | def receive = { 71 | case msg => 72 | println(s"Hera received ${msg}") 73 | childRef ! msg 74 | Thread.sleep(100) 75 | } 76 | } 77 | 78 | object Supervision extends App { 79 | // Create the 'supervision' actor system 80 | val system = ActorSystem("supervision") 81 | 82 | // Create Hera Actor 83 | val hera = system.actorOf(Props[Hera], "hera") 84 | 85 | // hera ! "Resume" 86 | // Thread.sleep(1000) 87 | // println() 88 | 89 | // hera ! "Restart" 90 | // Thread.sleep(1000) 91 | // println() 92 | 93 | hera ! "Stop" 94 | Thread.sleep(1000) 95 | println() 96 | 97 | system.terminate() 98 | } -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/sharding/ShardingApp.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.sharding 2 | 3 | import com.typesafe.config.ConfigFactory 4 | 5 | import scala.concurrent.duration._ 6 | import akka.actor._ 7 | import akka.cluster.Cluster 8 | import akka.cluster.sharding.{ClusterSharding, ClusterShardingSettings} 9 | import akka.pattern.ask 10 | import akka.persistence.journal.leveldb.{SharedLeveldbStore, SharedLeveldbJournal} 11 | import akka.util.Timeout 12 | 13 | /** Created by luhtonen on 13/04/16. */ 14 | object ShardingApp extends App { 15 | startup(Seq("2551", "2552", "0")) 16 | 17 | def startup(ports: Seq[String]): Unit = { 18 | ports foreach { port => 19 | // Override the configuration of the port 20 | val config = ConfigFactory.parseString("akka.remote.netty.tcp.port=" + port). 21 | withFallback(ConfigFactory.load("sharding")) 22 | 23 | // Create an Akka system 24 | val system = ActorSystem("ClusterSystem", config) 25 | 26 | startupSharedJournal(system, startStore = (port == "2551"), path = 27 | ActorPath.fromString("akka.tcp://ClusterSystem@127.0.0.1:2551/user/store")) 28 | 29 | ClusterSharding(system).start( 30 | typeName = Counter.shardName, 31 | entityProps = Counter.props(), 32 | settings = ClusterShardingSettings(system), 33 | extractEntityId = Counter.idExtractor, 34 | extractShardId = Counter.shardResolver) 35 | 36 | if (port != "2551" && port != "2552"){ 37 | Cluster(system) registerOnMemberUp { 38 | system.actorOf(Props[Frontend], "frontend") 39 | } 40 | } 41 | } 42 | 43 | def startupSharedJournal(system: ActorSystem, startStore: Boolean, path: ActorPath): Unit = { 44 | // Start the shared journal one one node (don't crash this SPOF) 45 | // This will not be needed with a distributed journal 46 | if (startStore) 47 | system.actorOf(Props[SharedLeveldbStore], "store") 48 | // register the shared journal 49 | import system.dispatcher 50 | implicit val timeout = Timeout(15.seconds) 51 | val f = system.actorSelection(path) ? Identify(None) 52 | f.onSuccess { 53 | case ActorIdentity(_, Some(ref)) => 54 | SharedLeveldbJournal.setStore(ref, system) 55 | case _ => 56 | system.log.error("Shared journal not started at {}", path) 57 | system.terminate() 58 | } 59 | f.onFailure { 60 | case _ => 61 | system.log.error("Lookup of shared journal at {} timed out", path) 62 | system.terminate() 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /akka-cluster/src/main/scala/org/elu/akka/singleton/SingletonApp.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.singleton 2 | 3 | import com.typesafe.config.ConfigFactory 4 | 5 | import scala.concurrent.duration._ 6 | import akka.actor._ 7 | import akka.pattern.ask 8 | import akka.cluster.Cluster 9 | import akka.cluster.singleton.{ClusterSingletonManager, ClusterSingletonManagerSettings} 10 | import akka.persistence.journal.leveldb.{SharedLeveldbStore, SharedLeveldbJournal} 11 | import akka.util.Timeout 12 | 13 | /** Created by luhtonen on 13/04/16. */ 14 | object SingletonApp extends App { 15 | startup(Seq("2551", "2552", "0")) 16 | 17 | def startup(ports: Seq[String]): Unit = { 18 | ports foreach { port => 19 | 20 | // Override the configuration of the port 21 | val config = ConfigFactory.parseString("akka.remote.netty.tcp.port=" + port). 22 | withFallback(ConfigFactory.load("singleton")) 23 | 24 | // Create an Akka system 25 | val system = ActorSystem("ClusterSystem", config) 26 | 27 | startupSharedJournal(system, startStore = (port == "2551"), path = 28 | ActorPath.fromString("akka.tcp://ClusterSystem@127.0.0.1:2551/user/store")) 29 | 30 | val master = system.actorOf(ClusterSingletonManager.props( 31 | singletonProps = Props[Master], 32 | terminationMessage = PoisonPill, 33 | settings = ClusterSingletonManagerSettings(system).withRole(None) 34 | ), name = "master") 35 | 36 | 37 | Cluster(system) registerOnMemberUp { 38 | system.actorOf(Worker.props, name = "worker") 39 | } 40 | 41 | if (port != "2551" && port != "2552"){ 42 | Cluster(system) registerOnMemberUp { 43 | system.actorOf(Frontend.props, name = "frontend") 44 | } 45 | } 46 | } 47 | 48 | def startupSharedJournal(system: ActorSystem, startStore: Boolean, path: ActorPath): Unit = { 49 | // Start the shared journal one one node (don't crash this SPOF) 50 | // This will not be needed with a distributed journal 51 | if (startStore) 52 | system.actorOf(Props[SharedLeveldbStore], "store") 53 | // register the shared journal 54 | import system.dispatcher 55 | implicit val timeout = Timeout(15.seconds) 56 | val f = (system.actorSelection(path) ? Identify(None)) 57 | f.onSuccess { 58 | case ActorIdentity(_, Some(ref)) => 59 | SharedLeveldbJournal.setStore(ref, system) 60 | case _ => 61 | system.log.error("Shared journal not started at {}", path) 62 | system.terminate() 63 | } 64 | f.onFailure { 65 | case _ => 66 | system.log.error("Lookup of shared journal at {} timed out", path) 67 | system.terminate() 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /playing-with-actors/src/main/scala/org/elu/akka/TalkToActor.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{ActorSystem, Props, Actor, ActorRef} 4 | import akka.pattern.ask 5 | import akka.util.Timeout 6 | import org.elu.akka.Checker.{BlackUser, CheckUser, WhiteUser} 7 | import org.elu.akka.Recorder.NewUser 8 | import org.elu.akka.Storage.AddUser 9 | 10 | import scala.concurrent.duration._ 11 | import scala.language.postfixOps 12 | 13 | case class User(username: String, email: String) 14 | 15 | object Recorder { 16 | sealed trait RecorderMsg 17 | // Recorder Messages 18 | case class NewUser(user: User) extends RecorderMsg 19 | 20 | def props(checker: ActorRef, storage: ActorRef) = Props(new Recorder(checker, storage)) 21 | } 22 | 23 | object Checker { 24 | sealed trait CheckerMsg 25 | // Checker Messages 26 | case class CheckUser(user: User) extends CheckerMsg 27 | 28 | sealed trait CheckerResponse 29 | // Checker Responses 30 | case class BlackUser(user: User) extends CheckerResponse 31 | case class WhiteUser(user: User) extends CheckerResponse 32 | } 33 | 34 | object Storage { 35 | sealed trait StorageMsg 36 | // Storage Messages 37 | case class AddUser(user: User) extends StorageMsg 38 | } 39 | 40 | class Storage extends Actor { 41 | var users = List.empty[User] 42 | 43 | def receive = { 44 | case AddUser(user: User) => 45 | println(s"Storage: $user added") 46 | users = user :: users 47 | } 48 | } 49 | 50 | class Checker extends Actor { 51 | val blackList = List( 52 | User("Adam", "adam@mail.com") 53 | ) 54 | 55 | def receive = { 56 | case CheckUser(user: User) if blackList.contains(user) => 57 | println(s"Checker: $user in the blacklist") 58 | sender() ! BlackUser(user) 59 | case CheckUser(user: User) => 60 | println(s"Checker: $user is not in the blacklist") 61 | sender() ! WhiteUser(user) 62 | } 63 | } 64 | 65 | class Recorder(checker: ActorRef, storage: ActorRef) extends Actor { 66 | import scala.concurrent.ExecutionContext.Implicits.global 67 | 68 | implicit val timeout = Timeout(5 seconds) 69 | 70 | def receive = { 71 | case NewUser(user) => 72 | checker ? CheckUser(user) map { 73 | case WhiteUser(user) => 74 | storage ! AddUser(user) 75 | case BlackUser(user) => 76 | println(s"Recorder $user is in the blacklist") 77 | } 78 | } 79 | } 80 | 81 | object TalkToActor extends App { 82 | 83 | // Create the 'talk-to-actor' actor system 84 | val system = ActorSystem("talk-to-actor") 85 | 86 | // Create the 'checker' actor 87 | val checker = system.actorOf(Props[Checker], "checker") 88 | 89 | // Create the 'storage' actor 90 | val storage = system.actorOf(Props[Storage], "storage") 91 | 92 | // Create the 'recorder' actor 93 | val recorder = system.actorOf(Recorder.props(checker, storage), "recorder") 94 | 95 | //send NewUser Message to Recorder 96 | recorder ! Recorder.NewUser(User("Jon", "jon@packt.com")) 97 | 98 | Thread.sleep(100) 99 | 100 | //shutdown system 101 | system.terminate() 102 | 103 | } -------------------------------------------------------------------------------- /persistent-fsm/src/main/scala/org/elu/akka/Account.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.persistence.fsm.PersistentFSM 4 | import akka.persistence.fsm.PersistentFSM.FSMState 5 | 6 | import scala.reflect._ 7 | 8 | /** Created by luhtonen on 12/04/16. */ 9 | object Account { 10 | 11 | // Account States 12 | sealed trait State extends FSMState 13 | case object Empty extends State { 14 | override def identifier = "Empty" 15 | } 16 | case object Active extends State { 17 | override def identifier = "Active" 18 | } 19 | 20 | // Account Data 21 | sealed trait Data { 22 | val amount: Float 23 | } 24 | case object ZeroBalance extends Data { 25 | override val amount: Float = 0.0f 26 | } 27 | case class Balance(override val amount: Float) extends Data 28 | 29 | // Domain Events (Persist events) 30 | sealed trait DomainEvent 31 | case class AcceptedTransaction(amount: Float, `type`: TransactionType) extends DomainEvent 32 | case class RejectedTransaction(amount: Float, `type`: TransactionType, reason: String) extends DomainEvent 33 | 34 | // Transaction Types 35 | sealed trait TransactionType 36 | case object CR extends TransactionType 37 | case object DR extends TransactionType 38 | 39 | // Commands 40 | case class Operation(amount: Float, `type`: TransactionType) 41 | } 42 | 43 | class Account extends PersistentFSM[Account.State, Account.Data, Account.DomainEvent] { 44 | import Account._ 45 | 46 | override def persistenceId: String = "account" 47 | 48 | override def applyEvent(evt: DomainEvent, currentData: Data): Data = { 49 | evt match { 50 | case AcceptedTransaction(amount, CR) => 51 | val newAmount = currentData.amount + amount 52 | println(s"Your new balance is ${newAmount}") 53 | Balance(newAmount) 54 | case AcceptedTransaction(amount, DR) => 55 | val newAmount = currentData.amount - amount 56 | println(s"Your new balance is ${newAmount}") 57 | if (newAmount > 0) { 58 | Balance(newAmount) 59 | } else { 60 | ZeroBalance 61 | } 62 | case RejectedTransaction(_, _, reason) => 63 | println(s"RejectedTransaction with reason: ${reason}") 64 | currentData 65 | } 66 | } 67 | 68 | override def domainEventClassTag: ClassTag[DomainEvent] = classTag[DomainEvent] 69 | 70 | startWith(Empty, ZeroBalance) 71 | 72 | when(Empty) { 73 | case Event(Operation(amount, CR), _) => 74 | println(s"Hi, It's your first Credit Operation.") 75 | goto(Active) applying AcceptedTransaction(amount, CR) 76 | case Event(Operation(amount, DR), _) => 77 | println(s"Sorry your account has zero balance.") 78 | stay applying RejectedTransaction(amount, DR, "Balance is Zero") 79 | } 80 | 81 | when(Active) { 82 | case Event(Operation(amount, CR), _) => 83 | stay applying AcceptedTransaction(amount, CR) 84 | case Event(Operation(amount, DR), balance) => 85 | val newBalance = balance.amount - amount 86 | if (newBalance > 0) { 87 | stay applying AcceptedTransaction(amount, DR) 88 | } else if (newBalance == 0) { 89 | goto(Empty) applying AcceptedTransaction(amount, DR) 90 | } else { 91 | stay applying RejectedTransaction(amount, DR, "balance doesn't cover this operation") 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /playing-with-actors/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 2: Playing with Actors 2 | This chapter introduces Actor system, hierarchical structure, components and lifecycle. Examples of this chapter show how to create actor with props, how to communicate with the actors as well as actor supervision strategy and monitoring capability. 3 | 4 | ## Running examples 5 | It is possible to define project's main class in `build.sbt` file like following: 6 | 7 | mainClass := org.elu.akka.Creation 8 | 9 | But this is not required. In all examples in this series `mainClass` is not defined and SBT tries to detect main class. Those are Scala classes or objects, which extends `scala.App` trait. If multiple main classes are detected, SBT show warning and ask to select class to run: 10 | 11 | [warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list 12 | 13 | Multiple main classes detected, select one to run: 14 | 15 | [1] org.elu.akka.Creation 16 | [2] org.elu.akka.Monitoring 17 | [3] org.elu.akka.Supervision 18 | [4] org.elu.akka.TalkToActor 19 | 20 | Enter number: 21 | 22 | Just press the number of the main class to run it. 23 | 24 | ## Notes about Scala traits and case classes 25 | In most of the examples messages are defined in the combination of `sealed trait` and `case class` or `case object`. 26 | 27 | `sealed trait` or `sealed class` can be extended only in the same source file as its declaration. `sealed trait` is usually used in combination with case classes or case objects for pattern matching and allow the compiler to perform exhaustiveness checking on the match expression. In simpler words, this means the compiler will shout at us if we miss out a case in our structural recursion. 28 | 29 | `case class` and `case object` are used to define value object, which can be used in pattern matching as case option. Difference between class and object is that class must have at least one parameter, for example in `TalkToActor.scala`: 30 | 31 | sealed trait RecorderMsg 32 | case class NewUser(user: User) extends RecorderMsg 33 | 34 | When there is not need to pass the parameters to the case class `case object` should be used instead, for example in `ActorCreation.scala`: 35 | 36 | sealed trait PlayMsg 37 | case object StopMusic extends PlayMsg 38 | case object StartMusic extends PlayMsg 39 | 40 | Use of no-arg case classes, for example like following `case class StopMusic extends PlayMsg`, is deprecated and may lead to unexpected results. For example following pattern matching wouldn't work: `case StopMusic => println("I don't want to stop music")`. This deprecation warning can be fixed by defining empty parameter list for the case class like following `case class StopMusic() extends PlayMsg`, but using `case object` is preferred. 41 | 42 | ## Other notes 43 | In many `App` objects or classes before terminating the Akka system can be seen following line: 44 | 45 | Thread.sleep(100) 46 | 47 | This tells the application to way for a specified amount of milliseconds (in some cases it is 100 ms some times it is 1000ms) to give the Akka time to process all messages. If you try to remove those lines, you might see that some messages were not processed, because `ActorSystem` is terminated before those messages were processed. 48 | 49 | In some videos at the end of `App` can be seen following line: 50 | 51 | system.shutdown() 52 | 53 | This is deprecated method, `terminate()` should be used instead: 54 | 55 | system.terminate() -------------------------------------------------------------------------------- /balancing-workload/src/main/scala/org/elu/akka/Master.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Terminated, Actor, ActorLogging, ActorRef} 4 | 5 | object MasterWorkerProtocol { 6 | // Messages from Workers 7 | case class WorkerCreated(worker: ActorRef) 8 | case class WorkerRequestsWork(worker: ActorRef) 9 | case class WorkIsDone(worker: ActorRef) 10 | 11 | // Messages to Workers 12 | case class WorkToBeDone(work: Any) 13 | case object WorkIsReady 14 | case object NoWorkToBeDone 15 | } 16 | 17 | class Master extends Actor with ActorLogging { 18 | import MasterWorkerProtocol._ 19 | import scala.collection.mutable 20 | 21 | // Holds known workers and what they may be working on 22 | val workers = mutable.Map.empty[ActorRef, Option[(ActorRef, Any)]] 23 | // Holds the incoming list of work to be done as well 24 | // as the memory of who asked for it 25 | val workQ = mutable.Queue.empty[(ActorRef, Any)] 26 | 27 | // Notifies workers that there's work available, provided they're 28 | // not already working on something 29 | def notifyWorkers(): Unit = { 30 | if (workQ.nonEmpty) { 31 | workers.foreach { 32 | case (worker, m) if m.isEmpty => worker ! WorkIsReady 33 | case _ => 34 | } 35 | } 36 | } 37 | 38 | def receive = { 39 | // Worker is alive. Add him to the list, watch him for 40 | // death, and let him know if there's work to be done 41 | case WorkerCreated(worker) => 42 | log.info("Worker created: {}", worker) 43 | context.watch(worker) 44 | workers += (worker -> None) 45 | notifyWorkers() 46 | 47 | // A worker wants more work. If we know about him, he's not 48 | // currently doing anything, and we've got something to do, 49 | // give it to him. 50 | case WorkerRequestsWork(worker) => 51 | log.info("Worker requests work: {}", worker) 52 | if (workers.contains(worker)) { 53 | if (workQ.isEmpty) 54 | worker ! NoWorkToBeDone 55 | else if (workers(worker).isEmpty) { 56 | val (workSender, work) = workQ.dequeue() 57 | workers += (worker -> Some(workSender -> work)) 58 | // Use the special form of 'tell' that lets us supply 59 | // the sender 60 | worker.tell(WorkToBeDone(work), workSender) 61 | } 62 | } 63 | 64 | // Worker has completed its work and we can clear it out 65 | case WorkIsDone(worker) => 66 | if (!workers.contains(worker)) 67 | log.error("Blurgh! {} said it's done work but we didn't know about him", worker) 68 | else 69 | workers += (worker -> None) 70 | 71 | // A worker died. If he was doing anything then we need 72 | // to give it to someone else so we just add it back to the 73 | // master and let things progress as usual 74 | case Terminated(worker) => 75 | if (workers.contains(worker) && workers(worker).isDefined) { 76 | log.error("Blurgh! {} died while processing {}", worker, workers(worker)) 77 | // Send the work that it was doing back to ourselves for processing 78 | val (workSender, work) = workers(worker).get 79 | self.tell(work, workSender) 80 | } 81 | workers -= worker 82 | context.unwatch(worker) 83 | 84 | // Anything other than our own protocol is "work to be done" 85 | case work => 86 | log.info("Queueing {}", work) 87 | workQ.enqueue(sender -> work) 88 | notifyWorkers() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /balancing-workload/src/test/scala/org/elu/akka/WorkerSpec.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka 2 | 3 | import akka.actor.{Props, ActorPath, ActorRef, ActorSystem} 4 | import scala.concurrent.duration._ 5 | import akka.pattern.{ask, pipe} 6 | import akka.testkit.{ImplicitSender, TestKit} 7 | import akka.util.Timeout 8 | import org.scalatest.MustMatchers 9 | import org.scalatest.{BeforeAndAfterAll, WordSpecLike} 10 | 11 | import scala.concurrent.{Await, Future} 12 | import scala.language.postfixOps 13 | 14 | class TestWorker(masterLocation: ActorPath) extends Worker(masterLocation) { 15 | // We'll use the current dispatcher for the execution context. 16 | // You can use whatever you want. 17 | implicit val ec = context.dispatcher 18 | 19 | def doWork(workSender: ActorRef, msg: Any): Unit = { 20 | Future { 21 | workSender ! msg 22 | WorkComplete("done") 23 | } pipeTo self 24 | } 25 | } 26 | 27 | class BadTestWorker(masterLocation: ActorPath) extends Worker(masterLocation) { 28 | // We'll use the current dispatcher for the execution context. 29 | // You can use whatever you want. 30 | implicit val ec = context.dispatcher 31 | 32 | def doWork(workSender: ActorRef, msg: Any): Unit = context.stop(self) 33 | } 34 | 35 | class WorkerSpec extends TestKit(ActorSystem("WorkerSpec")) 36 | with ImplicitSender 37 | with WordSpecLike 38 | with BeforeAndAfterAll 39 | with MustMatchers { 40 | 41 | implicit val ec = system.dispatcher 42 | 43 | implicit val askTimeout = Timeout(1 second) 44 | override def afterAll() { 45 | system.terminate() 46 | } 47 | 48 | def worker(name: String) = system.actorOf(Props( 49 | new TestWorker(ActorPath.fromString( 50 | "akka://%s/user/%s".format(system.name, name))))) 51 | 52 | def badWorker(name: String) = system.actorOf(Props( 53 | new BadTestWorker(ActorPath.fromString( 54 | "akka://%s/user/%s".format(system.name, name))))) 55 | 56 | "Worker" should { 57 | "work" in { 58 | // Spin up the master 59 | val m = system.actorOf(Props[Master], "master") 60 | // Create three workers 61 | val w1 = worker("master") 62 | val w2 = worker("master") 63 | val w3 = worker("master") 64 | // Send some work to the master 65 | m ! "Hithere" 66 | m ! "Guys" 67 | m ! "So" 68 | m ! "What's" 69 | m ! "Up?" 70 | // We should get it all back 71 | expectMsgAllOf("Hithere", "Guys", "So", "What's", "Up?") 72 | } 73 | 74 | "still work if one dies" in { //{2 75 | // Spin up the master 76 | val m = system.actorOf(Props[Master], "master2") 77 | // Create three workers 78 | val w1 = worker("master2") 79 | val w2 = badWorker("master2") 80 | // Send some work to the master 81 | m ! "Hithere" 82 | m ! "Guys" 83 | m ! "So" 84 | m ! "What's" 85 | m ! "Up?" 86 | // We should get it all back 87 | expectMsgAllOf("Hithere", "Guys", "So", "What's", "Up?") 88 | } 89 | 90 | "work with Futures" in { 91 | // Spin up the master 92 | val m = system.actorOf(Props[Master], "master3") 93 | // Create three workers 94 | val w1 = worker("master3") 95 | val w2 = worker("master3") 96 | val w3 = worker("master3") 97 | val fs = Future.sequence(List("Hithere", "Guys", "So", "What's", "Up?").map { s => m ? s }) 98 | Await.result(fs, 1 second) must be (List("Hithere", "Guys", "So", "What's", "Up?")) 99 | // We should get it all back 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /stream-io/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 7: Working with Akka Streams 2 | ## Working with Stream IO 3 | Here's can be found simple example on how to use Akka Stream IO API. 4 | 5 | ### Notes about range creation 6 | In the video sample code in `WriteStream` object in `isPrime()` function on the last line was created the range from number `2` to `n - 1` with the following code: 7 | 8 | (2 to (n - 1)) 9 | 10 | For the situations when range need to be created until the certain number excluding actual number `Scala` have `until` keyword. So example above can be written as following: 11 | 12 | (2 until n) 13 | 14 | It might not save the typing but it will make the intension of the code more clear. 15 | 16 | ### Notes about usage of toMat() function 17 | In the video's original code in `WriteStream` object `fileSink` was created using toMat() function with manually written function: 18 | 19 | val fileSink = Flow[Int] 20 | .map(i => ByteString(i.toString)) 21 | .toMat(sink)((_, bytesWritten) => bytesWritten) 22 | 23 | Where last part `((_, bytesWritten) => bytesWritten)` is this manually written function, which selects only right side argument and discard left side argument. 24 | 25 | `toMat()` ScalaDoc suggest to use internally optimized `Keep.left` and `Keep.right` combiners: 26 | > It is recommended to use the internally optimized `Keep.left` and `Keep.right` combiners where appropriate instead of manually writing functions that pass through one of the values. 27 | 28 | In the example about to select only right side argument `Keep.right` should be used instead of the function: 29 | 30 | val fileSink = Flow[Int] 31 | .map(i => ByteString(i.toString)) 32 | .toMat(sink)(Keep.right) 33 | 34 | This syntax is more clear and as `ScalaDoc` suggested this is more optimized code. 35 | 36 | ### Migration notes 37 | File Streams have been changed a lot from experimental version 1.0. 38 | 39 | #### Sink.synchronousFile() is removed 40 | The `Sink.synchronousFile()` function is removed and replaced with `FileIO.toFile()` function. 41 | 42 | The following original code from `WriteStream` object 43 | 44 | val sink = Sink.synchronousFile(new File("target/prime.txt")) 45 | 46 | does not work anymore and should be replaced with the new code 47 | 48 | val sink = FileIO.toFile(new File("target/prime.txt")) 49 | 50 | #### Source.synchronousFile() is removed 51 | The `Source.synchronousFile()` function is removed and replaced with `FileIO.fromFile()` function. 52 | 53 | The following original code from `ReadStream` object 54 | 55 | val source = Source.synchronousFile(logFile) 56 | 57 | does not work anymore and should be replaced with the new code 58 | 59 | val source = FileIO.fromFile(logFile) 60 | 61 | #### FlowGraph is removed 62 | As in previous section was already mentioned `FlowGraph` is removed and should be replaced with `GraphDSL`. 63 | 64 | The following code from original video source: 65 | 66 | val g = FlowGraph.closed(fileSink, consoleSink)((file, _) => file) { implicit builder => 67 | (file, console) => 68 | import FlowGraph.Implicits._ 69 | 70 | val broadCast = builder.add(Broadcast[Int](2)) 71 | 72 | source ~> broadCast ~> file 73 | broadCast ~> console 74 | 75 | }.run() 76 | 77 | must be migrated as following: 78 | 79 | val g = RunnableGraph.fromGraph(GraphDSL.create(fileSink, consoleSink)(Keep.left) { implicit builder => 80 | (file, console) => 81 | import GraphDSL.Implicits._ 82 | 83 | val broadCast = builder.add(Broadcast[Int](2)) 84 | 85 | source ~> broadCast ~> file 86 | broadCast ~> console 87 | 88 | ClosedShape 89 | }).run() 90 | 91 | Note the use of `(Keep.left)` in `GraphDSL.create()` function call instead of manually defined function `((file, _) => file)`. -------------------------------------------------------------------------------- /throttler-messages/src/main/scala/org/elu/akka/throttle/TimerBasedThrottler.scala: -------------------------------------------------------------------------------- 1 | package org.elu.akka.throttle 2 | 3 | /** 4 | * Copyright (C) 2009-2016 Lightbend Inc. 5 | */ 6 | 7 | import scala.concurrent.duration.{ Duration, FiniteDuration } 8 | import scala.collection.immutable.{ Queue ⇒ Q } 9 | import akka.actor.{ ActorRef, Actor, FSM } 10 | import Throttler._ 11 | import TimerBasedThrottler._ 12 | import java.util.concurrent.TimeUnit 13 | 14 | /** 15 | * @see [[akka.contrib.throttle.TimerBasedThrottler]] 16 | * @see [[akka.contrib.throttle.Throttler.Rate]] 17 | * @see [[akka.contrib.throttle.Throttler.SetRate]] 18 | * @see [[akka.contrib.throttle.Throttler.SetTarget]] 19 | */ 20 | object Throttler { 21 | /** 22 | * A rate used for throttling. 23 | * 24 | * Scala API: There are some shorthands available to construct rates: 25 | * {{{ 26 | * import java.util.concurrent.TimeUnit._ 27 | * import scala.concurrent.duration.{ Duration, FiniteDuration } 28 | * 29 | * val rate1 = 1 msgsPer (1, SECONDS) 30 | * val rate2 = 1 msgsPer Duration(1, SECONDS) 31 | * val rate3 = 1 msgsPer (1 seconds) 32 | * val rate4 = 1 msgsPerSecond 33 | * val rate5 = 1 msgsPerMinute 34 | * val rate6 = 1 msgsPerHour 35 | * }}} 36 | * 37 | * @param numberOfCalls the number of calls that may take place in a period 38 | * @param duration the length of the period 39 | * @see [[akka.contrib.throttle.Throttler]] 40 | */ 41 | final case class Rate(val numberOfCalls: Int, val duration: FiniteDuration) { 42 | /** 43 | * The duration in milliseconds. 44 | */ 45 | def durationInMillis(): Long = duration.toMillis 46 | } 47 | 48 | /** 49 | * Set the target of a throttler. 50 | * 51 | * You may change a throttler's target at any time. 52 | * 53 | * Notice that the messages sent by the throttler to the target will have the original sender (and 54 | * not the throttler) as the sender. (In Akka terms, the throttler `forward`s the message.) 55 | * 56 | * @param target if `target` is `None`, the throttler will stop delivering messages and the messages already received 57 | * but not yet delivered, as well as any messages received in the future will be queued 58 | * and eventually be delivered when a new target is set. If `target` is not `None`, the currently queued messages 59 | * as well as any messages received in the future will be delivered to the new target at a rate not exceeding the current throttler's rate. 60 | */ 61 | final case class SetTarget(target: Option[ActorRef]) { 62 | /** 63 | * Java API: 64 | * @param target if `target` is `null`, the throttler will stop delivering messages and the messages already received 65 | * but not yet delivered, as well as any messages received in the future will be queued 66 | * and eventually be delivered when a new target is set. If `target` is not `null`, the currently queued messages 67 | * as well as any messages received in the future will be delivered to the new target at a rate not exceeding 68 | * the current throttler's rate. 69 | */ 70 | def this(target: ActorRef) = this(Option(target)) 71 | } 72 | 73 | /** 74 | * Set the rate of a throttler. 75 | * 76 | * You may change a throttler's rate at any time. 77 | * 78 | * @param rate the rate at which messages will be delivered to the target of the throttler 79 | */ 80 | final case class SetRate(rate: Rate) 81 | 82 | /** 83 | * Helper for some syntactic sugar. 84 | * 85 | * @see [[akka.contrib.throttle.Throttler.Rate]] 86 | */ 87 | implicit class RateInt(val numberOfCalls: Int) extends AnyVal { 88 | def msgsPer(duration: Int, timeUnit: TimeUnit) = Rate(numberOfCalls, Duration(duration, timeUnit)) 89 | def msgsPer(duration: FiniteDuration) = Rate(numberOfCalls, duration) 90 | def msgsPerSecond = Rate(numberOfCalls, Duration(1, TimeUnit.SECONDS)) 91 | def msgsPerMinute = Rate(numberOfCalls, Duration(1, TimeUnit.MINUTES)) 92 | def msgsPerHour = Rate(numberOfCalls, Duration(1, TimeUnit.HOURS)) 93 | } 94 | } 95 | 96 | /** 97 | * INTERNAL API 98 | */ 99 | private[throttle] object TimerBasedThrottler { 100 | case object Tick 101 | 102 | // States of the FSM: A `TimerBasedThrottler` is in state `Active` iff the timer is running. 103 | sealed trait State 104 | case object Idle extends State 105 | case object Active extends State 106 | 107 | // Messages, as we queue them to be sent later 108 | final case class Message(message: Any, sender: ActorRef) 109 | 110 | // The data of the FSM 111 | final case class Data(target: Option[ActorRef], 112 | callsLeftInThisPeriod: Int, 113 | queue: Q[Message]) 114 | } 115 | 116 | /** 117 | * A throttler that uses a timer to control the message delivery rate. 118 | * 119 | * == Throttling == 120 | * A throttler is an actor that is defined through a target actor and a rate 121 | * (of type [[akka.contrib.throttle.Throttler.Rate]]). You set or change the target and rate at any time through the 122 | * [[akka.contrib.throttle.Throttler.SetTarget]] and [[akka.contrib.throttle.Throttler.SetRate]] 123 | * messages, respectively. When you send the throttler any other message `msg`, it will 124 | * put the message `msg` into an internal queue and eventually send all queued messages to the target, at 125 | * a speed that respects the given rate. If no target is currently defined then the messages will be queued 126 | * and will be delivered as soon as a target gets set. 127 | * 128 | * A throttler understands actor messages of type 129 | * [[akka.contrib.throttle.Throttler.SetTarget]], [[akka.contrib.throttle.Throttler.SetRate]], in 130 | * addition to any other messages, which the throttler will consider as messages to be sent to 131 | * the target. 132 | * 133 | * == Transparency == 134 | * Notice that the throttler `forward`s messages, i.e., the target will see the original message sender 135 | * (and not the throttler) as the sender of the message. 136 | * 137 | * == Persistence == 138 | * Throttlers usually use an internal queue to keep the messages that need to be sent to the target. 139 | * You therefore cannot rely on the throttler's inbox size in order to learn how much messages are 140 | * outstanding. 141 | * 142 | * It is left to the implementation whether the internal queue is persisted over application restarts or 143 | * actor failure. 144 | * 145 | * == Processing messages == 146 | * The target should process messages as fast as possible. If the target requires substantial time to 147 | * process messages, it should distribute its work to other actors (using for example something like 148 | * a `BalancingDispatcher`), otherwise the resulting system will always work below 149 | * the threshold rate. 150 | * 151 | * Example: Suppose the throttler has a rate of 3msg/s and the target requires 1s to process a message. 152 | * This system will only process messages at a rate of 1msg/s: the target will receive messages at at most 3msg/s 153 | * but as it handles them synchronously and each of them takes 1s, its inbox will grow and grow. In such 154 | * a situation, the target should distribute its messages to a set of worker actors so that individual messages 155 | * can be handled in parallel. 156 | * 157 | * ==Example== 158 | * For example, if you set a rate like "3 messages in 1 second", the throttler 159 | * will send the first three messages immediately to the target actor but will need to impose a delay before 160 | * sending out further messages: 161 | * {{{ 162 | * // A simple actor that prints whatever it receives 163 | * class Printer extends Actor { 164 | * def receive = { 165 | * case x => println(x) 166 | * } 167 | * } 168 | * 169 | * val printer = system.actorOf(Props[Printer], "printer") 170 | * 171 | * // The throttler for this example, setting the rate 172 | * val throttler = system.actorOf(Props(classOf[TimerBasedThrottler], 3 msgsPer 1.second)) 173 | * 174 | * // Set the target 175 | * throttler ! SetTarget(Some(printer)) 176 | * // These three messages will be sent to the printer immediately 177 | * throttler ! "1" 178 | * throttler ! "2" 179 | * throttler ! "3" 180 | * // These two will wait at least until 1 second has passed 181 | * throttler ! "4" 182 | * throttler ! "5" 183 | * }}} 184 | * 185 | * ==Implementation notes== 186 | * This throttler implementation internally installs a timer that repeats every `rate.durationInMillis` and enables `rate.numberOfCalls` 187 | * additional calls to take place. A `TimerBasedThrottler` uses very few system resources, provided the rate's duration is not too 188 | * fine-grained (which would cause a lot of timer invocations); for example, it does not store the calling history 189 | * as other throttlers may need to do. 190 | * 191 | * However, a `TimerBasedThrottler` only provides ''weak guarantees'' on the rate (see also 192 | * this blog post): 193 | * 194 | * - Only ''delivery'' times are taken into account: if, for example, the throttler is used to throttle 195 | * requests to an external web service then only the start times of the web requests are considered. 196 | * If a web request takes very long on the server then more than `rate.numberOfCalls`-many requests 197 | * may be observed on the server in an interval of duration `rate.durationInMillis()`. 198 | * - There may be intervals of duration `rate.durationInMillis()` that contain more than `rate.numberOfCalls` 199 | * message deliveries: a `TimerBasedThrottler` only makes guarantees for the intervals 200 | * of its ''own'' timer, namely that no more than `rate.numberOfCalls`-many messages are delivered within such intervals. Other intervals on the 201 | * timeline may contain more calls. 202 | * 203 | * For some applications, these guarantees may not be sufficient. 204 | * 205 | * ==Known issues== 206 | * 207 | * - If you change the rate using `SetRate(rate)`, the actual rate may in fact be higher for the 208 | * overlapping period (i.e., `durationInMillis()`) of the new and old rate. Therefore, 209 | * changing the rate frequently is not recommended with the current implementation. 210 | * - The queue of messages to be delivered is not persisted in any way; actor or system failure will 211 | * cause the queued messages to be lost. 212 | * 213 | * @see [[akka.contrib.throttle.Throttler]] 214 | */ 215 | class TimerBasedThrottler(var rate: Rate) extends Actor with FSM[State, Data] { 216 | startWith(Idle, Data(None, rate.numberOfCalls, Q())) 217 | 218 | // Idle: no messages, or target not set 219 | when(Idle) { 220 | // Set the rate 221 | case Event(SetRate(rate), d) ⇒ 222 | this.rate = rate 223 | stay using d.copy(callsLeftInThisPeriod = rate.numberOfCalls) 224 | 225 | // Set the target 226 | case Event(SetTarget(t @ Some(_)), d) if !d.queue.isEmpty ⇒ 227 | goto(Active) using deliverMessages(d.copy(target = t)) 228 | case Event(SetTarget(t), d) ⇒ 229 | stay using d.copy(target = t) 230 | 231 | // Queuing 232 | case Event(msg, d @ Data(None, _, queue)) ⇒ 233 | stay using d.copy(queue = queue.enqueue(Message(msg, context.sender()))) 234 | case Event(msg, d @ Data(Some(_), _, Seq())) ⇒ 235 | goto(Active) using deliverMessages(d.copy(queue = Q(Message(msg, context.sender())))) 236 | // Note: The case Event(msg, t @ Data(Some(_), _, _, Seq(_*))) should never happen here. 237 | } 238 | 239 | when(Active) { 240 | // Set the rate 241 | case Event(SetRate(rate), d) ⇒ 242 | this.rate = rate 243 | // Note: this should be improved (see "Known issues" in class comments) 244 | stopTimer() 245 | startTimer(rate) 246 | stay using d.copy(callsLeftInThisPeriod = rate.numberOfCalls) 247 | 248 | // Set the target (when the new target is None) 249 | case Event(SetTarget(None), d) ⇒ 250 | // Note: We do not yet switch to state `Inactive` because we need the timer to tick once more before 251 | stay using d.copy(target = None) 252 | 253 | // Set the target (when the new target is not None) 254 | case Event(SetTarget(t @ Some(_)), d) ⇒ 255 | stay using d.copy(target = t) 256 | 257 | // Tick after a `SetTarget(None)`: take the additional permits and go to `Idle` 258 | case Event(Tick, d @ Data(None, _, _)) ⇒ 259 | goto(Idle) using d.copy(callsLeftInThisPeriod = rate.numberOfCalls) 260 | 261 | // Period ends and we have no more messages: take the additional permits and go to `Idle` 262 | case Event(Tick, d @ Data(_, _, Seq())) ⇒ 263 | goto(Idle) using d.copy(callsLeftInThisPeriod = rate.numberOfCalls) 264 | 265 | // Period ends and we get more occasions to send messages 266 | case Event(Tick, d @ Data(_, _, _)) ⇒ 267 | stay using deliverMessages(d.copy(callsLeftInThisPeriod = rate.numberOfCalls)) 268 | 269 | // Queue a message (when we cannot send messages in the current period anymore) 270 | case Event(msg, d @ Data(_, 0, queue)) ⇒ 271 | stay using d.copy(queue = queue.enqueue(Message(msg, context.sender()))) 272 | 273 | // Queue a message (when we can send some more messages in the current period) 274 | case Event(msg, d @ Data(_, _, queue)) ⇒ 275 | stay using deliverMessages(d.copy(queue = queue.enqueue(Message(msg, context.sender())))) 276 | } 277 | 278 | onTransition { 279 | case Idle -> Active ⇒ startTimer(rate) 280 | case Active -> Idle ⇒ stopTimer() 281 | } 282 | 283 | initialize() 284 | 285 | private def startTimer(rate: Rate) = setTimer("morePermits", Tick, rate.duration, true) 286 | private def stopTimer() = cancelTimer("morePermits") 287 | 288 | /** 289 | * Send as many messages as we can (while respecting the rate) to the target and 290 | * return the state data (with the queue containing the remaining ones). 291 | */ 292 | private def deliverMessages(data: Data): Data = { 293 | val queue = data.queue 294 | val nrOfMsgToSend = scala.math.min(queue.length, data.callsLeftInThisPeriod) 295 | 296 | queue.take(nrOfMsgToSend).foreach(x ⇒ data.target.get.tell(x.message, x.sender)) 297 | 298 | data.copy(queue = queue.drop(nrOfMsgToSend), callsLeftInThisPeriod = data.callsLeftInThisPeriod - nrOfMsgToSend) 299 | } 300 | } --------------------------------------------------------------------------------