├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.sbt ├── chapter-channels ├── build.sbt ├── project │ └── plugins.sbt ├── scala.sbt └── src │ ├── main │ └── scala │ │ └── aia │ │ └── channels │ │ ├── DeadLetterControl.scala │ │ ├── Orders.scala │ │ └── StateEndpoint.scala │ ├── multi-jvm │ ├── resources │ │ └── application.conf │ └── scala │ │ └── aia │ │ └── channels │ │ └── ProxyMultiJvm.scala │ └── test │ └── scala │ └── aia │ └── channels │ ├── DeadLetterTest.scala │ └── EventStreamTest.scala ├── chapter-cluster ├── build.sbt ├── project │ ├── WordsBuild.scala │ └── plugins.sbt ├── scala.sbt └── src │ ├── main │ ├── resources │ │ ├── frontend-cluster.conf │ │ ├── logback.xml │ │ ├── master.conf │ │ ├── repl-cluster-node2.conf │ │ ├── repl-cluster-node3.conf │ │ ├── repl-cluster-node4.conf │ │ ├── repl-cluster-node5.conf │ │ ├── seed-node1.conf │ │ ├── seed.conf │ │ ├── singlenode.conf │ │ └── worker.conf │ └── scala │ │ └── aia │ │ └── cluster │ │ └── words │ │ ├── ClusterDomainEventListener.scala │ │ ├── JobMaster.scala │ │ ├── JobReceptionist.scala │ │ ├── JobWorker.scala │ │ ├── Main.scala │ │ └── ReceptionistRouterLookup.scala │ ├── multi-jvm │ ├── resources │ │ └── words.txt │ └── scala │ │ └── aia │ │ └── cluster │ │ └── words │ │ ├── STMultiNodeSpec.scala │ │ ├── WordsClusterSpec.scala │ │ └── WordsClusterSpecConfig.scala │ └── test │ └── scala │ └── aia │ └── cluster │ └── words │ ├── LocalWordsSpec.scala │ └── StopSystemAfterAll.scala ├── chapter-conf-deploy ├── build.sbt ├── project │ └── plugins.sbt ├── scala.sbt └── src │ ├── main │ ├── resources │ │ ├── log4j.xml │ │ └── reference.conf │ └── scala │ │ └── aia │ │ └── deploy │ │ ├── BootHello.scala │ │ └── HelloWorld.scala │ ├── test │ ├── resources │ │ ├── application.conf │ │ ├── included.conf │ │ ├── lift.conf │ │ ├── load.conf │ │ └── reference.conf │ └── scala │ │ └── aia │ │ ├── config │ │ └── ConfigTest.scala │ │ └── deploy │ │ └── HelloWorldTest.scala │ └── universal │ └── conf │ ├── application.conf │ └── logback.xml ├── chapter-fault-tolerance ├── build.sbt └── src │ ├── main │ └── scala │ │ └── aia │ │ └── faulttolerance │ │ ├── LifeCycleHooks.scala │ │ ├── LogProcessing1.scala │ │ ├── LogProcessing2.scala │ │ ├── LogProcessing3.scala │ │ └── Termination.scala │ └── test │ └── scala │ └── aia │ └── faulttolerance │ └── LifeCycleHooksTest.scala ├── chapter-futures ├── README ├── build.sbt ├── project │ └── plugins.sbt ├── scala.sbt └── src │ ├── main │ └── scala │ │ └── com │ │ └── goticks │ │ ├── TicketInfo.scala │ │ └── TicketInfoService.scala │ └── test │ └── scala │ └── com │ └── goticks │ └── GetTicketInfoSpec.scala ├── chapter-integration ├── activemq-data │ └── localhost │ │ └── scheduler │ │ └── scheduleDB.redo ├── build.sbt ├── project │ └── build.properties ├── scala.sbt └── src │ ├── main │ ├── resources │ │ └── application.conf │ └── scala │ │ └── aia │ │ └── integration │ │ ├── CamelRest.scala │ │ ├── OrderServiceApi.scala │ │ ├── OrderServiceApp.scala │ │ └── Orders.scala │ └── test │ └── scala │ └── aia │ └── integration │ ├── CamelRestTest.scala │ ├── ConsumerTest.scala │ └── OrderServiceTest.scala ├── chapter-java ├── build.sbt ├── project │ ├── build.properties │ └── plugins.sbt ├── scala.sbt └── src │ └── main │ ├── java │ └── com │ │ └── goticks │ │ ├── a1_create │ │ ├── BoxOffice.java │ │ ├── Main.java │ │ ├── TicketSeller1.java │ │ ├── TicketSeller2.java │ │ └── TicketSeller3.java │ │ ├── a2_send1_tell │ │ ├── BoxOffice.java │ │ ├── Main.java │ │ └── TicketSeller.java │ │ ├── a2_send2_ask │ │ ├── BoxOffice.java │ │ ├── Main.java │ │ ├── MusicSeller.java │ │ ├── SportsSeller.java │ │ └── TicketSeller.java │ │ ├── a3_supervisor │ │ ├── BoxOffice.java │ │ ├── Main.java │ │ ├── MusicSeller.java │ │ ├── Shop.java │ │ ├── SportsSeller.java │ │ └── TicketSeller.java │ │ ├── a4_become1 │ │ ├── BoxOffice.java │ │ ├── Main.java │ │ └── TicketSeller.java │ │ └── a4_become2_fsm │ │ ├── BoxOffice.java │ │ ├── Main.java │ │ └── TicketSeller.java │ └── resources │ ├── goticks.conf │ └── logback.xml ├── chapter-looking-ahead ├── build.sbt ├── project │ ├── build.properties │ └── plugins.sbt ├── scala.sbt └── src │ ├── main │ ├── resources │ │ └── application.conf │ └── scala │ │ └── aia │ │ └── next │ │ ├── Basket.scala │ │ ├── Items.scala │ │ ├── Shopper.scala │ │ └── TypedBasket.scala │ └── test │ ├── resources │ └── application.conf │ └── scala │ └── aia │ └── next │ ├── BasketSpec.scala │ └── PersistenceSpec.scala ├── chapter-perf ├── README.md ├── build.sbt ├── scala.sbt └── src │ ├── main │ └── scala │ │ └── aia │ │ └── performance │ │ ├── SimulatorSystem.scala │ │ └── monitor │ │ ├── CalculateStatistics.scala │ │ ├── MonitorActor.scala │ │ ├── MonitorMailbox.scala │ │ └── MonitorStatisticsActor.scala │ └── test │ ├── resources │ ├── monitor │ │ └── mailbox.conf │ └── performance │ │ ├── dispatcher.conf │ │ └── through.conf │ └── scala │ └── aia │ └── performance │ ├── dispatcher │ ├── DispatcherInitTest.scala │ ├── DispatcherPinnedTest.scala │ ├── DispatcherSeparateTest.scala │ ├── DispatcherThreadPoolTest.scala │ ├── DispatcherThreads2Test.scala │ ├── DispatcherThreadsTest.scala │ └── DispatcherThroughputTest.scala │ ├── monitor │ ├── CalculateStatisticsTest.scala │ ├── MonitorActorTest.scala │ └── MonitorMailboxTest.scala │ └── throughput │ ├── ThroughputCPUTest.scala │ ├── ThroughputServiceTimeTest.scala │ └── ThroughputTest.scala ├── chapter-persistence ├── build.sbt ├── project │ ├── build.properties │ └── plugins.sbt ├── scala.sbt └── src │ ├── main │ ├── resources │ │ └── application.conf │ └── scala │ │ └── aia │ │ └── persistence │ │ ├── Basket.scala │ │ ├── Items.scala │ │ ├── LocalShoppers.scala │ │ ├── PaymentHistory.scala │ │ ├── Serializers.scala │ │ ├── Settings.scala │ │ ├── Shopper.scala │ │ ├── ShoppersSingleton.scala │ │ ├── SingletonMain.scala │ │ ├── Wallet.scala │ │ ├── calculator │ │ ├── Calculator.scala │ │ ├── CalculatorHistory.scala │ │ └── CalculatorMain.scala │ │ ├── rest │ │ ├── ShopperMarshalling.scala │ │ ├── ShopperService.scala │ │ └── ShoppersServiceSupport.scala │ │ └── sharded │ │ ├── ShardedMain.scala │ │ ├── ShardedShopper.scala │ │ └── ShardedShoppers.scala │ └── test │ ├── resources │ └── application.conf │ └── scala │ └── aia │ └── persistence │ ├── BasketQuerySpec.scala │ ├── BasketSpec.scala │ ├── LocalShoppersSpec.scala │ ├── PersistenceSpec.scala │ ├── ShopperSpec.scala │ └── calculator │ └── CalculatorSpec.scala ├── chapter-remoting ├── Procfile ├── build.sbt ├── project │ ├── GoticksBuild.scala │ ├── build.properties │ └── plugins.sbt ├── scala.sbt └── src │ ├── main │ ├── resources │ │ ├── backend.conf │ │ ├── frontend-remote-deploy.conf │ │ ├── frontend.conf │ │ ├── logback.xml │ │ └── singlenode.conf │ └── scala │ │ └── com │ │ └── goticks │ │ ├── BackendMain.scala │ │ ├── BackendRemoteDeployMain.scala │ │ ├── BoxOffice.scala │ │ ├── EventMarshalling.scala │ │ ├── FrontendMain.scala │ │ ├── FrontendRemoteDeployMain.scala │ │ ├── FrontendRemoteDeployWatchMain.scala │ │ ├── RemoteBoxOfficeForwarder.scala │ │ ├── RemoteLookupProxy.scala │ │ ├── RequestTimeout.scala │ │ ├── RestApi.scala │ │ ├── SingleNodeMain.scala │ │ ├── Startup.scala │ │ └── TicketSeller.scala │ ├── multi-jvm │ └── scala │ │ └── com │ │ └── goticks │ │ ├── ClientServerConfig.scala │ │ ├── ClientServerSpec.scala │ │ └── STMultiNodeSpec.scala │ └── test │ └── scala │ └── com │ └── goticks │ ├── BoxOfficeSpec.scala │ ├── StopSystemAfterAll.scala │ └── TicketSellerSpec.scala ├── chapter-routing ├── build.sbt ├── scala.sbt └── src │ ├── main │ └── scala │ │ └── aia │ │ └── routing │ │ ├── HashRouting.scala │ │ ├── ImageProcessing.scala │ │ ├── PerfRouting.scala │ │ ├── Routing.scala │ │ └── SlipRouter.scala │ └── test │ └── scala │ └── aia │ └── routing │ ├── HashRoutingTest.scala │ ├── MsgRoutingTest.scala │ ├── PerfRoutingTest.scala │ ├── RouteSlipTest.scala │ └── StateRoutingTest.scala ├── chapter-state ├── build.sbt ├── scala.sbt └── src │ ├── main │ └── scala │ │ └── aia │ │ └── state │ │ ├── Agent.scala │ │ └── Inventory.scala │ └── test │ └── scala │ └── aia │ └── state │ ├── AgentTest.scala │ └── InventoryTest.scala ├── chapter-stream-integration ├── build.sbt ├── scala.sbt └── src │ ├── main │ ├── resources │ │ └── application.conf │ └── scala │ │ └── aia │ │ └── stream │ │ └── integration │ │ ├── OrderServiceApi.scala │ │ ├── OrderServiceApp.scala │ │ ├── Orders.scala │ │ └── ProcessOrders.scala │ └── test │ ├── resources │ └── logback.xml │ └── scala │ └── aia │ └── stream │ └── integration │ ├── ConsumerTest.scala │ └── OrderServiceTest.scala ├── chapter-stream ├── build.sbt ├── project │ ├── build.properties │ └── plugins.sbt ├── scala.sbt └── src │ ├── main │ ├── resources │ │ └── application.conf │ └── scala │ │ └── aia │ │ └── stream │ │ ├── BidiEventFilter.scala │ │ ├── ContentNegLogsApi.scala │ │ ├── ContentNegLogsApp.scala │ │ ├── Event.scala │ │ ├── EventFilter.scala │ │ ├── EventMarshalling.scala │ │ ├── EventUnmarshaller.scala │ │ ├── FanLogsApi.scala │ │ ├── FanLogsApp.scala │ │ ├── FileArg.scala │ │ ├── GenerateLogFile.scala │ │ ├── LogEntityMarshaller.scala │ │ ├── LogJson.scala │ │ ├── LogStreamProcessor.scala │ │ ├── LogStreamProcessorApi.scala │ │ ├── LogStreamProcessorApp.scala │ │ ├── LogsApi.scala │ │ ├── LogsApp.scala │ │ ├── Metric.scala │ │ ├── MetricMarshalling.scala │ │ ├── Notification.scala │ │ ├── NotificationMarshalling.scala │ │ ├── ResumingEventFilter.scala │ │ └── StreamingCopy.scala │ └── test │ └── scala │ └── aia │ └── stream │ ├── LogStreamProcessorSpec.scala │ └── StopSystemAfterAll.scala ├── chapter-structure ├── build.sbt ├── scala.sbt └── src │ ├── main │ └── scala │ │ └── aia │ │ └── structure │ │ ├── Filters.scala │ │ └── ScatterGather.scala │ └── test │ └── scala │ └── aia │ └── structure │ ├── AggregatorTest.scala │ ├── PipeAndFilterTest.scala │ ├── RecipientListTest.scala │ └── ScatterGatherTest.scala ├── chapter-testdriven ├── build.sbt ├── scala.sbt └── src │ ├── main │ └── scala │ │ └── aia │ │ └── testdriven │ │ └── Greeter.scala │ └── test │ └── scala │ └── aia │ └── testdriven │ ├── EchoActorTest.scala │ ├── FilteringActorTest.scala │ ├── Greeter01Test.scala │ ├── Greeter02Test.scala │ ├── GreetingsSpec.scala │ ├── SendingActorTest.scala │ ├── SilentActor01Test.scala │ ├── SilentActorNextTest.scala │ └── StopSystemAfterAll.scala ├── chapter-up-and-running ├── Procfile ├── README.md ├── build.sbt ├── project │ ├── build.properties │ └── plugins.sbt ├── scala.sbt └── src │ ├── main │ ├── resources │ │ ├── application.conf │ │ └── logback.xml │ └── scala │ │ └── com │ │ └── goticks │ │ ├── BoxOffice.scala │ │ ├── EventMarshalling.scala │ │ ├── Main.scala │ │ ├── RestApi.scala │ │ └── TicketSeller.scala │ └── test │ ├── resources │ └── application.conf │ └── scala │ └── com │ └── goticks │ ├── BoxOfficeSpec.scala │ ├── StopSystemAfterAll.scala │ └── TicketSellerSpec.scala └── project ├── build.properties └── plugins.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | dist/* 6 | target/ 7 | lib_managed/ 8 | src_managed/ 9 | project/boot/ 10 | project/plugins/project/ 11 | 12 | # IDE specific 13 | .scala_dependencies 14 | .classpath 15 | *.iml 16 | .idea/ 17 | .idea_modules/ 18 | .project 19 | .settings/ 20 | *.sublime-project 21 | *.sublime-workspace 22 | /.env 23 | atlassian-ide-plugin.xml 24 | 25 | # Mac OSX specific 26 | .DS_Store 27 | 28 | #ActiveMQ specific 29 | *.data 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.12.3 4 | jdk: 5 | - oraclejdk8 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Manning Publications, Co. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "all" 2 | 3 | version := "1.0" 4 | 5 | organization := "com.manning" 6 | 7 | lazy val channels = project.in(file("chapter-channels")) 8 | 9 | lazy val cluster = project.in(file("chapter-cluster")) 10 | 11 | lazy val conf = project.in(file("chapter-conf-deploy")) 12 | 13 | lazy val fault = project.in(file("chapter-fault-tolerance")) 14 | 15 | lazy val futures = project.in(file("chapter-futures")) 16 | 17 | lazy val integration = project.in(file("chapter-integration")) 18 | 19 | lazy val java = project.in(file("chapter-java")) 20 | 21 | lazy val looking = project.in(file("chapter-looking-ahead")) 22 | 23 | lazy val persistence = project.in(file("chapter-persistence")) 24 | 25 | lazy val remoting = project.in(file("chapter-remoting")) 26 | 27 | lazy val routing = project.in(file("chapter-routing")) 28 | 29 | lazy val state = project.in(file("chapter-state")) 30 | 31 | lazy val stream = project.in(file("chapter-stream")) 32 | 33 | lazy val `stream-integration` = project.in(file("chapter-stream-integration")) 34 | 35 | lazy val structure = project.in(file("chapter-structure")) 36 | 37 | lazy val test = project.in(file("chapter-testdriven")) 38 | 39 | lazy val up = project.in(file("chapter-up-and-running")) 40 | 41 | parallelExecution in Test := false 42 | -------------------------------------------------------------------------------- /chapter-channels/build.sbt: -------------------------------------------------------------------------------- 1 | import com.typesafe.sbt.SbtMultiJvm 2 | import com.typesafe.sbt.SbtMultiJvm.MultiJvmKeys.MultiJvm 3 | 4 | val akkaVersion = "2.5.4" 5 | 6 | val project = Project( 7 | id = "channels", 8 | base = file("."), 9 | settings = Project.defaultSettings ++ SbtMultiJvm.multiJvmSettings ++ Seq( 10 | name := "akka-sample-multi-node-scala", 11 | organization := "manning", 12 | version := "1.0", 13 | libraryDependencies ++= Seq( 14 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 15 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 16 | "com.typesafe.akka" %% "akka-remote" % akkaVersion, 17 | "com.typesafe.akka" %% "akka-multi-node-testkit" % akkaVersion, 18 | "com.typesafe.akka" %% "akka-contrib" % akkaVersion, 19 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 20 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 21 | ), 22 | // make sure that MultiJvm test are compiled by the default test compilation 23 | compile in MultiJvm <<= (compile in MultiJvm) triggeredBy (compile in Test), 24 | // disable parallel tests 25 | parallelExecution in Test := false, 26 | // make sure that MultiJvm tests are executed by the default test target, 27 | // and combine the results from ordinary test and multi-jvm tests 28 | executeTests in Test <<= (executeTests in Test, executeTests in MultiJvm) map { 29 | case (testResults, multiNodeResults) => 30 | val overall = 31 | if (testResults.overall.id < multiNodeResults.overall.id) 32 | multiNodeResults.overall 33 | else 34 | testResults.overall 35 | Tests.Output(overall, 36 | testResults.events ++ multiNodeResults.events, 37 | testResults.summaries ++ multiNodeResults.summaries) 38 | } 39 | ) 40 | ) configs (MultiJvm) 41 | -------------------------------------------------------------------------------- /chapter-channels/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.8") 2 | -------------------------------------------------------------------------------- /chapter-channels/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-channels/src/main/scala/aia/channels/DeadLetterControl.scala: -------------------------------------------------------------------------------- 1 | package aia.channels 2 | 3 | import akka.actor.Actor 4 | 5 | class EchoActor extends Actor { 6 | def receive = { 7 | case msg: AnyRef => 8 | sender() ! msg 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /chapter-channels/src/main/scala/aia/channels/Orders.scala: -------------------------------------------------------------------------------- 1 | package aia.channels 2 | 3 | case class Order(customerId: String, productId: String, number: Int) 4 | -------------------------------------------------------------------------------- /chapter-channels/src/main/scala/aia/channels/StateEndpoint.scala: -------------------------------------------------------------------------------- 1 | package aia.channels 2 | 3 | import akka.actor.Actor 4 | import java.util.Date 5 | 6 | case class StateEvent(time: Date, state: String) 7 | case class Connection(time: Date, connected: Boolean) 8 | 9 | class StateEndpoint extends Actor { 10 | def receive = { 11 | case Connection(time, true) => { 12 | context.system.eventStream.publish(new StateEvent(time, "Connected")) 13 | } 14 | case Connection(time, false) => { 15 | context.system.eventStream.publish(new StateEvent(time, "Disconnected")) 16 | } 17 | } 18 | } 19 | 20 | class SystemLog extends Actor { 21 | def receive = { 22 | case event: StateEvent => { 23 | } 24 | } 25 | } 26 | 27 | class SystemMonitor extends Actor { 28 | def receive = { 29 | case event: StateEvent => { 30 | } 31 | } 32 | } 33 | 34 | 35 | import akka.event.ActorEventBus 36 | import akka.event.{ LookupClassification, EventBus } 37 | 38 | class OrderMessageBus extends EventBus 39 | with LookupClassification 40 | with ActorEventBus { 41 | 42 | type Event = Order 43 | type Classifier = Boolean 44 | def mapSize = 2 45 | 46 | protected def classify(event: OrderMessageBus#Event) = { 47 | event.number > 1 48 | } 49 | 50 | protected def publish(event: OrderMessageBus#Event, 51 | subscriber: OrderMessageBus#Subscriber) { 52 | subscriber ! event 53 | } 54 | } 55 | 56 | 57 | class MyEventBus extends EventBus with LookupClassification 58 | with ActorEventBus { 59 | 60 | type Event = AnyRef 61 | def mapSize = 2 62 | type Classifier = String 63 | 64 | protected def classify(event: MyEventBus#Event) = { 65 | "TestBus" 66 | } 67 | 68 | protected def publish(event: MyEventBus#Event, 69 | subscriber: MyEventBus#Subscriber) { 70 | subscriber ! event 71 | } 72 | 73 | def subscribe(subscriber: Subscriber): Boolean = 74 | subscribers.put("TestBus", subscriber) 75 | } 76 | -------------------------------------------------------------------------------- /chapter-channels/src/multi-jvm/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | actor { 3 | provider = "akka.remote.RemoteActorRefProvider" 4 | } 5 | remote { 6 | enabled-transports = ["akka.remote.netty.tcp"] 7 | netty.tcp { 8 | hostname="127.0.0.1" 9 | port=2552 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /chapter-channels/src/test/scala/aia/channels/DeadLetterTest.scala: -------------------------------------------------------------------------------- 1 | package aia.channels 2 | 3 | import akka.testkit.{ ImplicitSender, TestProbe, TestKit } 4 | import akka.actor.{ PoisonPill, Props, DeadLetter, ActorSystem } 5 | import org.scalatest.{WordSpecLike, BeforeAndAfterAll, MustMatchers} 6 | import java.util.Date 7 | 8 | class DeadLetterTest extends TestKit(ActorSystem("DeadLetterTest")) 9 | with WordSpecLike with BeforeAndAfterAll with MustMatchers 10 | with ImplicitSender { 11 | 12 | override def afterAll() { 13 | system.terminate() 14 | } 15 | 16 | "DeadLetter" must { 17 | "catch messages send to deadLetters" in { 18 | val deadLetterMonitor = TestProbe() 19 | 20 | system.eventStream.subscribe( 21 | deadLetterMonitor.ref, 22 | classOf[DeadLetter]) 23 | 24 | val msg = new StateEvent(new Date(), "Connected") 25 | system.deadLetters ! msg 26 | 27 | val dead = deadLetterMonitor.expectMsgType[DeadLetter] 28 | dead.message must be(msg) 29 | dead.sender must be(testActor) 30 | dead.recipient must be(system.deadLetters) 31 | } 32 | "catch deadLetter messages send to deadLetters" in { 33 | 34 | val deadLetterMonitor = TestProbe() 35 | val actor = system.actorOf(Props[EchoActor]) 36 | 37 | system.eventStream.subscribe( 38 | deadLetterMonitor.ref, 39 | classOf[DeadLetter]) 40 | 41 | val msg = new Order("me", "Akka in Action", 1) 42 | val dead = DeadLetter(msg, testActor, actor) 43 | system.deadLetters ! dead 44 | 45 | deadLetterMonitor.expectMsg(dead) 46 | 47 | system.stop(actor) 48 | 49 | } 50 | 51 | "catch messages send to terminated Actor" in { 52 | 53 | val deadLetterMonitor = TestProbe() 54 | 55 | system.eventStream.subscribe( 56 | deadLetterMonitor.ref, 57 | classOf[DeadLetter]) 58 | 59 | val actor = system.actorOf(Props[EchoActor]) 60 | actor ! PoisonPill 61 | val msg = new Order("me", "Akka in Action", 1) 62 | actor ! msg 63 | 64 | val dead = deadLetterMonitor.expectMsgType[DeadLetter] 65 | dead.message must be(msg) 66 | dead.sender must be(testActor) 67 | dead.recipient must be(actor) 68 | 69 | } 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /chapter-cluster/build.sbt: -------------------------------------------------------------------------------- 1 | name := "words-cluster" 2 | 3 | version := "1.0" 4 | 5 | organization := "com.manning" 6 | 7 | libraryDependencies ++= { 8 | val akkaVersion = "2.5.4" 9 | Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-remote" % akkaVersion, 13 | "com.typesafe.akka" %% "akka-cluster" % akkaVersion, 14 | "com.typesafe.akka" %% "akka-multi-node-testkit" % akkaVersion % "test", 15 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 16 | "org.scalatest" %% "scalatest" % "3.0.0" % "test", 17 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 18 | "ch.qos.logback" % "logback-classic" % "1.0.10" 19 | ) 20 | } 21 | 22 | // Assembly settings 23 | mainClass in Global := Some("aia.cluster.words.Main") 24 | 25 | assemblyJarName in assembly := "words-node.jar" 26 | -------------------------------------------------------------------------------- /chapter-cluster/project/WordsBuild.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import com.typesafe.sbt.SbtMultiJvm 4 | import com.typesafe.sbt.SbtMultiJvm.MultiJvmKeys.{ MultiJvm } 5 | 6 | object WordsBuild extends Build { 7 | 8 | lazy val buildSettings = Defaults.defaultSettings ++ multiJvmSettings ++ Seq( 9 | crossPaths := false 10 | ) 11 | 12 | lazy val project = Project( 13 | id = "words-cluster", 14 | base = file("."), 15 | settings = buildSettings ++ Project.defaultSettings 16 | ) configs(MultiJvm) 17 | 18 | lazy val multiJvmSettings = SbtMultiJvm.multiJvmSettings ++ Seq( 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 | executeTests in Test <<= 25 | ((executeTests in Test), (executeTests in MultiJvm)) map { 26 | case ((testResults), (multiJvmResults)) => 27 | val overall = 28 | if (testResults.overall.id < multiJvmResults.overall.id) 29 | multiJvmResults.overall 30 | else 31 | testResults.overall 32 | Tests.Output(overall, 33 | testResults.events ++ multiJvmResults.events, 34 | testResults.summaries ++ multiJvmResults.summaries) 35 | } 36 | ) 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /chapter-cluster/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Classpaths.typesafeReleases 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0") 4 | 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0") 6 | 7 | addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.11") 8 | -------------------------------------------------------------------------------- /chapter-cluster/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/resources/frontend-cluster.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | stdout-loglevel = DEBUG 4 | loggers = ["akka.event.Logging$DefaultLogger"] 5 | 6 | actor { 7 | provider = "akka.cluster.ClusterActorRefProvider" 8 | 9 | } 10 | 11 | remote { 12 | enabled-transports = ["akka.remote.netty.tcp"] 13 | log-remote-lifecycle-events = off 14 | netty.tcp { 15 | hostname = "" 16 | port = 2551 17 | port = ${?NETTY_PORT} 18 | } 19 | } 20 | 21 | cluster { 22 | seed-nodes = [ 23 | "akka.tcp://goticks@127.0.0.1:2551"] 24 | 25 | auto-down = on 26 | } 27 | } 28 | 29 | spray { 30 | can { 31 | server { 32 | server-header = "GoTicks.com REST API" 33 | } 34 | } 35 | } 36 | 37 | http { 38 | host = "0.0.0.0" 39 | host = ${?HOST} 40 | port = 5000 41 | port = ${?PORT} 42 | } 43 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.out 5 | 6 | 7 | %-6level[%logger{0}]: %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/resources/master.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | stdout-loglevel = INFO 4 | loggers = ["akka.event.Logging$DefaultLogger"] 5 | 6 | actor { 7 | provider = "akka.cluster.ClusterActorRefProvider" 8 | } 9 | 10 | remote { 11 | enabled-transports = ["akka.remote.netty.tcp"] 12 | log-remote-lifecycle-events = off 13 | netty.tcp { 14 | hostname = "127.0.0.1" 15 | hostname = ${?HOST} 16 | port = ${PORT} 17 | } 18 | } 19 | 20 | cluster { 21 | seed-nodes = [ 22 | "akka.tcp://words@127.0.0.1:2551", 23 | "akka.tcp://words@127.0.0.1:2552", 24 | "akka.tcp://words@127.0.0.1:2553" 25 | ] 26 | roles = ["master"] 27 | auto-down = on 28 | 29 | role { 30 | seed.min-nr-of-members = 1 31 | master.min-nr-of-members = 1 32 | worker.min-nr-of-members = 2 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/resources/repl-cluster-node2.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | stdout-loglevel = INFO 4 | loggers = ["akka.event.Logging$DefaultLogger"] 5 | 6 | actor { 7 | provider = "akka.cluster.ClusterActorRefProvider" 8 | 9 | } 10 | 11 | remote { 12 | enabled-transports = ["akka.remote.netty.tcp"] 13 | log-remote-lifecycle-events = off 14 | netty.tcp { 15 | hostname = "" 16 | port = 2552 17 | port = ${?NETTY_PORT} 18 | } 19 | } 20 | 21 | cluster { 22 | seed-nodes = ["akka.tcp://sys@127.0.0.1:2551"] 23 | auto-down = off 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/resources/repl-cluster-node3.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | stdout-loglevel = INFO 4 | loggers = ["akka.event.Logging$DefaultLogger"] 5 | 6 | actor { 7 | provider = "akka.cluster.ClusterActorRefProvider" 8 | 9 | } 10 | 11 | remote { 12 | enabled-transports = ["akka.remote.netty.tcp"] 13 | log-remote-lifecycle-events = off 14 | netty.tcp { 15 | hostname = "" 16 | port = 2553 17 | port = ${?NETTY_PORT} 18 | } 19 | } 20 | 21 | cluster { 22 | auto-down = off 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/resources/repl-cluster-node4.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | stdout-loglevel = INFO 4 | loggers = ["akka.event.Logging$DefaultLogger"] 5 | 6 | actor { 7 | provider = "akka.cluster.ClusterActorRefProvider" 8 | 9 | } 10 | 11 | remote { 12 | enabled-transports = ["akka.remote.netty.tcp"] 13 | log-remote-lifecycle-events = off 14 | netty.tcp { 15 | hostname = "" 16 | port = 2554 17 | port = ${?NETTY_PORT} 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/resources/repl-cluster-node5.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | stdout-loglevel = INFO 4 | loggers = ["akka.event.Logging$DefaultLogger"] 5 | 6 | actor { 7 | provider = "akka.cluster.ClusterActorRefProvider" 8 | 9 | } 10 | 11 | remote { 12 | enabled-transports = ["akka.remote.netty.tcp"] 13 | log-remote-lifecycle-events = off 14 | netty.tcp { 15 | hostname = "" 16 | port = 2555 17 | port = ${?NETTY_PORT} 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/resources/seed-node1.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | stdout-loglevel = INFO 4 | loggers = ["akka.event.Logging$DefaultLogger"] 5 | 6 | actor { 7 | provider = "akka.cluster.ClusterActorRefProvider" 8 | 9 | } 10 | 11 | remote { 12 | enabled-transports = ["akka.remote.netty.tcp"] 13 | log-remote-lifecycle-events = off 14 | netty.tcp { 15 | hostname = "" 16 | port = 2551 17 | port = ${?NETTY_PORT} 18 | } 19 | } 20 | 21 | cluster { 22 | seed-nodes = [ 23 | "akka.tcp://sys@127.0.0.1:2551"] 24 | roles = ["seed"] 25 | auto-down = off 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/resources/seed.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | stdout-loglevel = INFO 4 | loggers = ["akka.event.Logging$DefaultLogger"] 5 | 6 | log-dead-letters = 0 7 | log-dead-letters-during-shutdown = off 8 | 9 | actor { 10 | provider = "akka.cluster.ClusterActorRefProvider" #// 11 | } 12 | 13 | remote { #// 14 | enabled-transports = ["akka.remote.netty.tcp"] 15 | log-remote-lifecycle-events = off 16 | netty.tcp { 17 | hostname = "127.0.0.1" 18 | hostname = ${?HOST} 19 | port = ${PORT} 20 | } 21 | } 22 | 23 | cluster { #// 24 | seed-nodes = [ 25 | "akka.tcp://words@127.0.0.1:2551", 26 | "akka.tcp://words@127.0.0.1:2552", 27 | "akka.tcp://words@127.0.0.1:2553" 28 | ] #// 29 | 30 | roles = ["seed"] #// 31 | 32 | role { 33 | seed.min-nr-of-members = 1 #// 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/resources/singlenode.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | stdout-loglevel = DEBUG 4 | loggers = ["akka.event.Logging$DefaultLogger"] 5 | 6 | } 7 | 8 | spray { 9 | can { 10 | server { 11 | server-header = "GoTicks.com REST API" 12 | } 13 | } 14 | } 15 | 16 | http { 17 | host = "0.0.0.0" 18 | host = ${?HOST} 19 | port = 5000 20 | port = ${?PORT} 21 | } 22 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/resources/worker.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | stdout-loglevel = INFO 4 | loggers = ["akka.event.Logging$DefaultLogger"] 5 | 6 | actor { 7 | provider = "akka.cluster.ClusterActorRefProvider" 8 | } 9 | 10 | remote { 11 | enabled-transports = ["akka.remote.netty.tcp"] 12 | log-remote-lifecycle-events = off 13 | netty.tcp { 14 | hostname = "127.0.0.1" 15 | hostname = ${?HOST} 16 | port = ${PORT} 17 | } 18 | } 19 | 20 | cluster { 21 | seed-nodes = [ 22 | "akka.tcp://words@127.0.0.1:2551", 23 | "akka.tcp://words@127.0.0.1:2552", 24 | "akka.tcp://words@127.0.0.1:2553" 25 | ] 26 | roles = ["worker"] 27 | auto-down = on 28 | role { 29 | seed.min-nr-of-members = 1 30 | worker.min-nr-of-members = 2 31 | master.min-nr-of-members = 1 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/scala/aia/cluster/words/ClusterDomainEventListener.scala: -------------------------------------------------------------------------------- 1 | package aia.cluster.words 2 | 3 | import akka.actor.{ActorLogging, Actor} 4 | 5 | import akka.cluster.{MemberStatus, Cluster} 6 | import akka.cluster.ClusterEvent._ 7 | 8 | class ClusterDomainEventListener extends Actor 9 | with ActorLogging { 10 | Cluster(context.system).subscribe(self, classOf[ClusterDomainEvent]) 11 | 12 | def receive ={ 13 | case MemberUp(member) => 14 | log.info(s"$member UP.") 15 | case MemberExited(member)=> 16 | log.info(s"$member EXITED.") 17 | case MemberRemoved(member, previousState)=> 18 | if(previousState == MemberStatus.Exiting) { 19 | log.info(s"Member $member Previously gracefully exited, REMOVED.") 20 | } else { 21 | log.info(s"$member Previously downed after unreachable, REMOVED.") 22 | } 23 | case UnreachableMember(member) => 24 | log.info(s"$member UNREACHABLE") 25 | case ReachableMember(member) => 26 | log.info(s"$member REACHABLE") 27 | case state: CurrentClusterState => 28 | log.info(s"Current state of the cluster: $state") 29 | } 30 | override def postStop(): Unit = { 31 | Cluster(context.system).unsubscribe(self) 32 | super.postStop() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/scala/aia/cluster/words/JobWorker.scala: -------------------------------------------------------------------------------- 1 | package aia.cluster 2 | package words 3 | 4 | import scala.concurrent.duration._ 5 | 6 | import akka.actor._ 7 | 8 | 9 | object JobWorker { 10 | def props = Props(new JobWorker) 11 | 12 | case class Work(jobName: String, master: ActorRef) 13 | case class Task(input: List[String], master: ActorRef) 14 | case object WorkLoadDepleted 15 | } 16 | 17 | class JobWorker extends Actor 18 | with ActorLogging { 19 | import JobMaster._ 20 | import JobWorker._ 21 | import context._ 22 | 23 | var processed = 0 24 | 25 | def receive = idle 26 | 27 | def idle: Receive = { 28 | case Work(jobName, master) => 29 | become(enlisted(jobName, master)) 30 | 31 | log.info(s"Enlisted, will start requesting work for job '${jobName}'.") 32 | master ! Enlist(self) 33 | master ! NextTask 34 | watch(master) 35 | 36 | setReceiveTimeout(30 seconds) 37 | } 38 | 39 | def enlisted(jobName: String, master: ActorRef): Receive = { 40 | case ReceiveTimeout => 41 | master ! NextTask 42 | 43 | case Task(textPart, master) => 44 | val countMap = processTask(textPart) 45 | processed = processed + 1 46 | master ! TaskResult(countMap) 47 | master ! NextTask 48 | 49 | case WorkLoadDepleted => 50 | log.info(s"Work load ${jobName} is depleted, retiring...") 51 | setReceiveTimeout(Duration.Undefined) 52 | become(retired(jobName)) 53 | 54 | case Terminated(master) => 55 | setReceiveTimeout(Duration.Undefined) 56 | log.error(s"Master terminated that ran Job ${jobName}, stopping self.") 57 | stop(self) 58 | } 59 | 60 | 61 | def retired(jobName: String): Receive = { 62 | case Terminated(master) => 63 | log.error(s"Master terminated that ran Job ${jobName}, stopping self.") 64 | stop(self) 65 | case _ => log.error("I'm retired.") 66 | } 67 | 68 | def processTask(textPart: List[String]): Map[String, Int] = { 69 | textPart.flatMap(_.split("\\W+")) 70 | .foldLeft(Map.empty[String, Int]) { 71 | (count, word) => 72 | if (word == "FAIL") throw new RuntimeException("SIMULATED FAILURE!") 73 | count + (word -> (count.getOrElse(word, 0) + 1)) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /chapter-cluster/src/main/scala/aia/cluster/words/Main.scala: -------------------------------------------------------------------------------- 1 | package aia.cluster 2 | package words 3 | 4 | import com.typesafe.config.ConfigFactory 5 | import akka.actor.{Props, ActorSystem} 6 | import akka.cluster.Cluster 7 | 8 | import JobReceptionist.JobRequest 9 | 10 | object Main extends App { 11 | val config = ConfigFactory.load() 12 | val system = ActorSystem("words", config) 13 | 14 | println(s"Starting node with roles: ${Cluster(system).selfRoles}") 15 | 16 | if(system.settings.config.getStringList("akka.cluster.roles").contains("master")) { 17 | Cluster(system).registerOnMemberUp { 18 | val receptionist = system.actorOf(Props[JobReceptionist], "receptionist") 19 | println("Master node is ready.") 20 | 21 | val text = List("this is a test", "of some very naive word counting", "but what can you say", "it is what it is") 22 | receptionist ! JobRequest("the first job", (1 to 100000).flatMap(i => text ++ text).toList) 23 | system.actorOf(Props(new ClusterDomainEventListener), "cluster-listener") 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /chapter-cluster/src/main/scala/aia/cluster/words/ReceptionistRouterLookup.scala: -------------------------------------------------------------------------------- 1 | package aia.cluster 2 | package words 3 | 4 | import akka.actor._ 5 | import akka.cluster.routing._ 6 | import akka.routing._ 7 | 8 | trait ReceptionistRouterLookup { this: Actor => 9 | def receptionistRouter = context.actorOf( 10 | ClusterRouterGroup( 11 | BroadcastGroup(Nil), 12 | ClusterRouterGroupSettings( 13 | totalInstances = 100, 14 | routeesPaths = List("/user/receptionist"), 15 | allowLocalRoutees = true, 16 | useRole = Some("master") 17 | ) 18 | ).props(), 19 | name = "receptionist-router") 20 | } 21 | -------------------------------------------------------------------------------- /chapter-cluster/src/multi-jvm/resources/words.txt: -------------------------------------------------------------------------------- 1 | All the world's a stage, 2 | And all the men and women merely players; 3 | They have their exits and their entrances, 4 | And one man in his time plays many parts, 5 | His acts being seven ages. At first, the infant, 6 | Mewling and puking in the nurse's arms. 7 | Then the whining schoolboy, with his satchel 8 | And shining morning face, creeping like snail 9 | Unwillingly to school. And then the lover, 10 | Sighing like furnace, with a woeful ballad 11 | Made to his mistress' eyebrow. Then a soldier, 12 | Full of strange oaths and bearded like the pard, 13 | Jealous in honor, sudden and quick in quarrel, 14 | Seeking the bubble reputation 15 | Even in the cannon's mouth. And then the justice, 16 | In fair round belly with good capon lined, 17 | With eyes severe and beard of formal cut, 18 | Full of wise saws and modern instances; 19 | And so he plays his part. The sixth age shifts 20 | Into the lean and slippered pantaloon, 21 | With spectacles on nose and pouch on side; 22 | His youthful hose, well saved, a world too wide 23 | For his shrunk shank, and his big manly voice, 24 | Turning again toward childish treble, pipes 25 | And whistles in his sound. Last scene of all, 26 | That ends this strange eventful history, 27 | Is second childishness and mere oblivion, 28 | Sans teeth, sans eyes, sans taste, sans everything. -------------------------------------------------------------------------------- /chapter-cluster/src/multi-jvm/scala/aia/cluster/words/STMultiNodeSpec.scala: -------------------------------------------------------------------------------- 1 | package aia.cluster 2 | package words 3 | 4 | import akka.remote.testkit.MultiNodeSpecCallbacks 5 | import org.scalatest._ 6 | import org.scalatest.MustMatchers 7 | 8 | trait STMultiNodeSpec extends MultiNodeSpecCallbacks 9 | with WordSpecLike with MustMatchers with BeforeAndAfterAll { 10 | 11 | override def beforeAll() = multiNodeSpecBeforeAll() 12 | 13 | override def afterAll() = multiNodeSpecAfterAll() 14 | } 15 | -------------------------------------------------------------------------------- /chapter-cluster/src/multi-jvm/scala/aia/cluster/words/WordsClusterSpecConfig.scala: -------------------------------------------------------------------------------- 1 | package aia.cluster 2 | package words 3 | 4 | import akka.remote.testkit.MultiNodeConfig 5 | import com.typesafe.config.ConfigFactory 6 | 7 | object WordsClusterSpecConfig extends MultiNodeConfig { 8 | val seed = role("seed") 9 | val master = role("master") 10 | val worker1 = role("worker-1") 11 | val worker2 = role("worker-2") 12 | 13 | commonConfig(ConfigFactory.parseString(""" 14 | akka.actor.provider="akka.cluster.ClusterActorRefProvider" 15 | # don't use sigar for tests, native lib not in path 16 | akka.cluster.metrics.collector-class = akka.cluster.JmxMetricsCollector 17 | """)) 18 | } 19 | -------------------------------------------------------------------------------- /chapter-cluster/src/test/scala/aia/cluster/words/StopSystemAfterAll.scala: -------------------------------------------------------------------------------- 1 | package aia.cluster.words 2 | 3 | import org.scalatest.{Suite, BeforeAndAfterAll} 4 | import akka.testkit.TestKit 5 | 6 | trait StopSystemAfterAll extends BeforeAndAfterAll { 7 | this: TestKit with Suite => 8 | override protected def afterAll(): Unit = { 9 | super.afterAll() 10 | system.terminate() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-conf-deploy/build.sbt: -------------------------------------------------------------------------------- 1 | name := "deploy" 2 | 3 | version := "1.0" 4 | 5 | organization := "manning" 6 | 7 | scalacOptions ++= Seq( 8 | "-deprecation", 9 | "-unchecked", 10 | "-Xlint", 11 | "-Ywarn-unused", 12 | "-Ywarn-dead-code", 13 | "-feature", 14 | "-language:_" 15 | ) 16 | 17 | enablePlugins(JavaAppPackaging) 18 | 19 | scriptClasspath +="../conf" 20 | 21 | libraryDependencies ++= { 22 | val akkaVersion = "2.5.4" 23 | Seq( 24 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 25 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 26 | "ch.qos.logback" % "logback-classic" % "1.0.13", 27 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 28 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 29 | ) 30 | } 31 | 32 | -------------------------------------------------------------------------------- /chapter-conf-deploy/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0") 2 | 3 | 4 | -------------------------------------------------------------------------------- /chapter-conf-deploy/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-conf-deploy/src/main/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /chapter-conf-deploy/src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | helloWorld { 2 | timer=5000 3 | } -------------------------------------------------------------------------------- /chapter-conf-deploy/src/main/scala/aia/deploy/BootHello.scala: -------------------------------------------------------------------------------- 1 | package aia.deploy 2 | 3 | 4 | import akka.actor.{ Props, ActorSystem } 5 | import scala.concurrent.duration._ 6 | 7 | object BootHello extends App { 8 | 9 | val system = ActorSystem("hellokernel") 10 | 11 | val actor = system.actorOf(Props[HelloWorld]) 12 | val config = system.settings.config 13 | val timer = config.getInt("helloWorld.timer") 14 | system.actorOf(Props( 15 | new HelloWorldCaller( 16 | timer millis, 17 | actor))) 18 | } 19 | -------------------------------------------------------------------------------- /chapter-conf-deploy/src/main/scala/aia/deploy/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | package aia.deploy 2 | 3 | import akka.actor.{ActorRef, ActorLogging, Actor} 4 | import scala.concurrent.duration._ 5 | 6 | 7 | class HelloWorld extends Actor 8 | with ActorLogging { 9 | 10 | def receive = { 11 | case msg: String => 12 | val hello = "Hello %s".format(msg) 13 | sender() ! hello 14 | log.info("Sent response {}",hello) 15 | } 16 | } 17 | 18 | 19 | 20 | class HelloWorldCaller(timer: FiniteDuration, actor: ActorRef) 21 | extends Actor with ActorLogging { 22 | 23 | case class TimerTick(msg: String) 24 | 25 | override def preStart(): Unit = { 26 | super.preStart() 27 | implicit val ec = context.dispatcher 28 | context.system.scheduler.schedule( 29 | timer, 30 | timer, 31 | self, 32 | new TimerTick("everybody")) 33 | } 34 | 35 | def receive = { 36 | case msg: String => log.info("received {}",msg) 37 | case tick: TimerTick => actor ! tick.msg 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /chapter-conf-deploy/src/test/resources/application.conf: -------------------------------------------------------------------------------- 1 | include "included" 2 | 3 | myTest { 4 | intParam=20 5 | applicationDesc="My Config Test" 6 | } 7 | myTestDefaults { 8 | applicationDesc="My Current Test" 9 | } -------------------------------------------------------------------------------- /chapter-conf-deploy/src/test/resources/included.conf: -------------------------------------------------------------------------------- 1 | myTestIncluded { 2 | intParam=20 3 | applicationDesc="My Include Test" 4 | } -------------------------------------------------------------------------------- /chapter-conf-deploy/src/test/resources/lift.conf: -------------------------------------------------------------------------------- 1 | myTestLift { 2 | myTest { 3 | applicationDesc="My Lift Test" 4 | } 5 | rootParam="root" 6 | } 7 | 8 | myTest { 9 | intParam=20 10 | applicationDesc="My Default Test" 11 | } -------------------------------------------------------------------------------- /chapter-conf-deploy/src/test/resources/load.conf: -------------------------------------------------------------------------------- 1 | myTestLoad { 2 | intParam=20 3 | applicationDesc="My Load Test" 4 | } -------------------------------------------------------------------------------- /chapter-conf-deploy/src/test/resources/reference.conf: -------------------------------------------------------------------------------- 1 | myTestDefaults { 2 | intParam=20 3 | applicationDesc="My Config Test" 4 | } -------------------------------------------------------------------------------- /chapter-conf-deploy/src/test/scala/aia/deploy/HelloWorldTest.scala: -------------------------------------------------------------------------------- 1 | package aia.deploy 2 | 3 | import org.scalatest.{BeforeAndAfterAll, WordSpecLike} 4 | import org.scalatest.MustMatchers 5 | import akka.testkit.{TestActorRef, ImplicitSender, TestKit} 6 | import akka.actor.ActorSystem 7 | 8 | class HelloWorldTest extends TestKit(ActorSystem("HelloWorldTest")) 9 | with ImplicitSender 10 | with WordSpecLike 11 | with MustMatchers 12 | with BeforeAndAfterAll { 13 | 14 | val actor = TestActorRef[HelloWorld] 15 | 16 | override def afterAll(): Unit = { 17 | system.terminate() 18 | } 19 | "HelloWorld" must { 20 | "reply when sending a string" in { 21 | actor ! "everybody" 22 | expectMsg("Hello everybody") 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter-conf-deploy/src/universal/conf/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loggers = ["akka.event.slf4j.Slf4jLogger"] 3 | 4 | # Options: ERROR, WARNING, INFO, DEBUG 5 | loglevel = "DEBUG" 6 | } 7 | 8 | 9 | -------------------------------------------------------------------------------- /chapter-conf-deploy/src/universal/conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | myApp.log 5 | 6 | 7 | %date %level [%thread] %logger{10} [%file:%line] %msg%n 8 | 9 | 10 | 11 | 12 | 14 | 15 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %m%n 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /chapter-fault-tolerance/build.sbt: -------------------------------------------------------------------------------- 1 | name := "fault-tolerance" 2 | 3 | version := "1.0" 4 | 5 | organization := "com.manning" 6 | 7 | scalaVersion := "2.12.3" 8 | 9 | libraryDependencies ++= { 10 | val akkaVersion = "2.5.4" 11 | Seq( 12 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 13 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 14 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 15 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /chapter-fault-tolerance/src/main/scala/aia/faulttolerance/LifeCycleHooks.scala: -------------------------------------------------------------------------------- 1 | package aia.faulttolerance 2 | 3 | import aia.faulttolerance.LifeCycleHooks.{ForceRestart, ForceRestartException} 4 | import akka.actor._ 5 | 6 | object LifeCycleHooks { 7 | 8 | object SampleMessage 9 | 10 | object ForceRestart 11 | 12 | private class ForceRestartException extends IllegalArgumentException("force restart") 13 | 14 | } 15 | 16 | class LifeCycleHooks extends Actor with ActorLogging { 17 | log.info("Constructor") 18 | 19 | override def preStart(): Unit = { 20 | log.info("preStart") 21 | } 22 | 23 | 24 | 25 | override def postStop(): Unit = { 26 | log.info("postStop") 27 | } 28 | 29 | 30 | 31 | override def preRestart(reason: Throwable, 32 | message: Option[Any]): Unit = { 33 | log.info(s"preRestart. Reason: $reason when handling message: $message") 34 | super.preRestart(reason, message) 35 | } 36 | 37 | 38 | 39 | override def postRestart(reason: Throwable): Unit = { 40 | log.info("postRestart") 41 | super.postRestart(reason) 42 | 43 | } 44 | 45 | 46 | def receive = { 47 | case ForceRestart => 48 | throw new ForceRestartException 49 | case msg: AnyRef => 50 | log.info(s"Received: '$msg'. Sending back") 51 | sender() ! msg 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /chapter-fault-tolerance/src/main/scala/aia/faulttolerance/Termination.scala: -------------------------------------------------------------------------------- 1 | package aia.faulttolerance 2 | 3 | import akka.actor._ 4 | import akka.actor.Terminated 5 | 6 | object DbStrategy2 { 7 | 8 | class DbWatcher(dbWriter: ActorRef) extends Actor with ActorLogging { 9 | context.watch(dbWriter) 10 | def receive = { 11 | case Terminated(actorRef) => 12 | log.warning("Actor {} terminated", actorRef) 13 | } 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /chapter-fault-tolerance/src/test/scala/aia/faulttolerance/LifeCycleHooksTest.scala: -------------------------------------------------------------------------------- 1 | package aia.faulttolerance 2 | 3 | import aia.faulttolerance.LifeCycleHooks.{ForceRestart, SampleMessage} 4 | import akka.actor._ 5 | import akka.testkit.TestKit 6 | import org.scalatest.{BeforeAndAfterAll, WordSpecLike} 7 | 8 | class LifeCycleHooksTest extends TestKit(ActorSystem("LifCycleTest")) with WordSpecLike with BeforeAndAfterAll { 9 | 10 | override def afterAll(): Unit = { 11 | system.terminate() 12 | } 13 | 14 | "The Child" must { 15 | "log lifecycle hooks" in { 16 | val testActorRef = system.actorOf( 17 | Props[LifeCycleHooks], "LifeCycleHooks") 18 | watch(testActorRef) 19 | testActorRef ! ForceRestart 20 | testActorRef.tell(SampleMessage, testActor) 21 | expectMsg(SampleMessage) 22 | system.stop(testActorRef) 23 | expectTerminated(testActorRef) 24 | 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /chapter-futures/README: -------------------------------------------------------------------------------- 1 | work in progress. -------------------------------------------------------------------------------- /chapter-futures/build.sbt: -------------------------------------------------------------------------------- 1 | name := "futures" 2 | 3 | version := "1.0" 4 | 5 | organization := "com.goticks" 6 | 7 | libraryDependencies ++= { 8 | val akkaVersion = "2.5.4" 9 | Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 13 | "org.scalatest" %% "scalatest" % "3.0.0" % "test", 14 | "com.github.nscala-time" %% "nscala-time" % "2.16.0" 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /chapter-futures/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Classpaths.typesafeResolver 2 | -------------------------------------------------------------------------------- /chapter-futures/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-futures/src/main/scala/com/goticks/TicketInfo.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import org.joda.time.{Duration, DateTime} 4 | 5 | case class TicketInfo(ticketNr: String, 6 | userLocation: Location, 7 | event: Option[Event]=None, 8 | travelAdvice: Option[TravelAdvice]=None, 9 | weather: Option[Weather]=None, 10 | suggestions: Seq[Event]=Seq()) 11 | 12 | case class Event(name: String,location: Location, time: DateTime) 13 | 14 | case class Artist(name: String, calendarUri: String) 15 | 16 | case class Location(lat: Double, lon: Double) 17 | 18 | case class RouteByCar(route: String, timeToLeave: DateTime, origin: Location, destination: Location, 19 | estimatedDuration: Duration, trafficJamTime: Duration) 20 | 21 | case class PublicTransportAdvice(advice: String, timeToLeave: DateTime, origin: Location, destination: Location, 22 | estimatedDuration: Duration) 23 | 24 | case class TravelAdvice(routeByCar: Option[RouteByCar]=None, 25 | publicTransportAdvice: Option[PublicTransportAdvice]=None) 26 | 27 | case class Weather(temperature: Int, precipitation: Boolean) 28 | -------------------------------------------------------------------------------- /chapter-integration/activemq-data/localhost/scheduler/scheduleDB.redo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akka-ja/akka-in-action/ce9942ec53169828647a8678ba1c4e3157111a6d/chapter-integration/activemq-data/localhost/scheduler/scheduleDB.redo -------------------------------------------------------------------------------- /chapter-integration/build.sbt: -------------------------------------------------------------------------------- 1 | name := "integration" 2 | 3 | version := "1.0" 4 | 5 | organization := "manning" 6 | 7 | libraryDependencies ++= { 8 | val akkaVersion = "2.5.4" 9 | val camelVersion = "2.17.7" 10 | val activeMQVersion = "5.4.1" 11 | val akkaHttpVersion = "10.0.10" 12 | Seq( 13 | "org.scala-lang.modules" %% "scala-xml" % "1.0.6", 14 | "com.typesafe.akka" %% "akka-camel" % akkaVersion, 15 | "net.liftweb" % "lift-json_2.10" % "3.0-M1", 16 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 17 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 18 | "com.typesafe.akka" %% "akka-http-core" % akkaHttpVersion, 19 | "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, 20 | "com.typesafe.akka" %% "akka-stream" % akkaVersion, 21 | "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion, 22 | "com.typesafe.akka" %% "akka-http-xml" % akkaHttpVersion, 23 | "ch.qos.logback" % "logback-classic" % "1.1.3", 24 | "commons-io" % "commons-io" % "2.0.1" % "test", 25 | "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % "test", 26 | "org.apache.camel" % "camel-mina" % camelVersion % "test", 27 | "org.apache.activemq" % "activemq-camel" % activeMQVersion % "test", 28 | "org.apache.activemq" % "activemq-core" % activeMQVersion % "test", 29 | "org.apache.camel" % "camel-jetty" % camelVersion % "test", 30 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 31 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 32 | ) 33 | } 34 | 35 | -------------------------------------------------------------------------------- /chapter-integration/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.0.0 2 | -------------------------------------------------------------------------------- /chapter-integration/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-integration/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | stdout-loglevel = INFO 4 | loggers = ["akka.event.slf4j.Slf4jLogger"] 5 | default-dispatcher { 6 | fork-join-executor { 7 | parallelism-min = 8 8 | } 9 | } 10 | http { 11 | server { 12 | server-header = "OrderService REST API" 13 | } 14 | } 15 | } 16 | 17 | http { 18 | host = "0.0.0.0" 19 | host = ${?HOST} 20 | port = 5000 21 | port = ${?PORT} 22 | } 23 | -------------------------------------------------------------------------------- /chapter-integration/src/main/scala/aia/integration/OrderServiceApp.scala: -------------------------------------------------------------------------------- 1 | package aia.integration 2 | 3 | import scala.concurrent.Future 4 | 5 | import akka.actor.{ ActorSystem , Actor, Props } 6 | import akka.event.Logging 7 | import akka.util.Timeout 8 | 9 | import akka.http.scaladsl.Http 10 | import akka.http.scaladsl.Http.ServerBinding 11 | import akka.http.scaladsl.server.Directives._ 12 | import akka.stream.ActorMaterializer 13 | 14 | import com.typesafe.config.{ Config, ConfigFactory } 15 | 16 | object OrderServiceApp extends App 17 | with RequestTimeout { 18 | val config = ConfigFactory.load() 19 | val host = config.getString("http.host") 20 | val port = config.getInt("http.port") 21 | 22 | implicit val system = ActorSystem() 23 | implicit val ec = system.dispatcher 24 | 25 | val processOrders = system.actorOf( 26 | Props(new ProcessOrders), "process-orders" 27 | ) 28 | 29 | val api = new OrderServiceApi(system, 30 | requestTimeout(config), 31 | processOrders).routes 32 | 33 | implicit val materializer = ActorMaterializer() 34 | val bindingFuture: Future[ServerBinding] = 35 | Http().bindAndHandle(api, host, port) 36 | 37 | val log = Logging(system.eventStream, "order-service") 38 | bindingFuture.map { serverBinding => 39 | log.info(s"Bound to ${serverBinding.localAddress} ") 40 | }.onFailure { 41 | case ex: Exception => 42 | log.error(ex, "Failed to bind to {}:{}!", host, port) 43 | system.terminate() 44 | } 45 | } 46 | 47 | 48 | trait RequestTimeout { 49 | import scala.concurrent.duration._ 50 | def requestTimeout(config: Config): Timeout = { 51 | val t = config.getString("akka.http.server.request-timeout") 52 | val d = Duration(t) 53 | FiniteDuration(d.length, d.unit) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /chapter-integration/src/test/scala/aia/integration/OrderServiceTest.scala: -------------------------------------------------------------------------------- 1 | package aia.integration 2 | 3 | import scala.concurrent.duration._ 4 | import scala.xml.NodeSeq 5 | import akka.actor.Props 6 | 7 | import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._ 8 | import akka.http.scaladsl.model.StatusCodes 9 | import akka.http.scaladsl.server._ 10 | import akka.http.scaladsl.testkit.ScalatestRouteTest 11 | 12 | import org.scalatest.{ Matchers, WordSpec } 13 | 14 | class OrderServiceTest extends WordSpec 15 | with Matchers 16 | with OrderService 17 | with ScalatestRouteTest { 18 | 19 | implicit val executionContext = system.dispatcher 20 | implicit val requestTimeout = akka.util.Timeout(1 second) 21 | val processOrders = 22 | system.actorOf(Props(new ProcessOrders), "orders") 23 | 24 | "The order service" should { 25 | "return NotFound if the order cannot be found" in { 26 | Get("/orders/1") ~> routes ~> check { 27 | status shouldEqual StatusCodes.NotFound 28 | } 29 | } 30 | 31 | "return the tracking order for an order that was posted" in { 32 | val xmlOrder = 33 | customer1 34 | Akka in action 35 |   10 36 | 37 | 38 | Post("/orders", xmlOrder) ~> routes ~> check { 39 | status shouldEqual StatusCodes.OK 40 | val xml = responseAs[NodeSeq] 41 | val id = (xml \\ "id").text.toInt 42 | val orderStatus = (xml \\ "status").text 43 | id shouldEqual 1 44 | orderStatus shouldEqual "received" 45 | } 46 | Get("/orders/1") ~> routes ~> check { 47 | status shouldEqual StatusCodes.OK 48 | val xml = responseAs[NodeSeq] 49 | val id = (xml \\ "id").text.toInt 50 | val orderStatus = (xml \\ "status").text 51 | id shouldEqual 1 52 | orderStatus shouldEqual "processing" 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /chapter-java/build.sbt: -------------------------------------------------------------------------------- 1 | name := "goticks-java" 2 | 3 | version := "1.0" 4 | 5 | javacOptions ++= Seq("-encoding", "UTF-8") 6 | 7 | libraryDependencies ++= { 8 | val akkaVersion = "2.5.4" 9 | Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 12 | "ch.qos.logback" % "logback-classic" % "1.1.3" 13 | ) 14 | } 15 | 16 | -------------------------------------------------------------------------------- /chapter-java/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.16 2 | -------------------------------------------------------------------------------- /chapter-java/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Classpaths.typesafeReleases 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5") 4 | 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.2.0") 6 | -------------------------------------------------------------------------------- /chapter-java/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a1_create/Main.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a1_create; 2 | 3 | import akka.actor.ActorRef; 4 | import akka.actor.ActorSystem; 5 | import com.typesafe.config.ConfigFactory; 6 | import scala.concurrent.duration.Duration; 7 | import scala.concurrent.Await; 8 | import com.goticks.a1_create.BoxOffice.*; 9 | 10 | // アクターの生成 11 | class Main { 12 | public static void main(String args[]) throws Exception { 13 | 14 | final ActorSystem system = ActorSystem.create("main", ConfigFactory.load("goticks")); 15 | final ActorRef boxOffice = system.actorOf(BoxOffice.props(), "boxOffice"); 16 | 17 | boxOffice.tell(new Initialize(), ActorRef.noSender()); 18 | boxOffice.tell(new Order(), ActorRef.noSender()); 19 | 20 | // 2秒後にシャットダウン 21 | system.scheduler().scheduleOnce(Duration.create(2, "seconds"), boxOffice, new BoxOffice.Shutdown(), system.dispatcher(), ActorRef.noSender()); 22 | Await.result(system.whenTerminated(), Duration.Inf()); 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a1_create/TicketSeller1.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a1_create; 2 | 3 | import akka.actor.AbstractActor; 4 | import akka.actor.Props; 5 | import akka.event.Logging; 6 | import akka.event.LoggingAdapter; 7 | 8 | // メッセージを受信したときの振る舞いを定義 9 | public class TicketSeller1 extends AbstractActor { 10 | static public Props props() { 11 | return Props.create(TicketSeller1.class, () -> new TicketSeller1()); 12 | } 13 | 14 | /** 注文メッセージ */ 15 | public static class Order { 16 | public Order() { 17 | } 18 | } 19 | 20 | private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); 21 | 22 | public TicketSeller1() { 23 | } 24 | 25 | @Override 26 | public Receive createReceive() { 27 | return receiveBuilder() 28 | // String 型のメッセージを受信した場合 29 | .match(String.class, msg-> log.info("received String message: {}", msg)) 30 | // Int 型のメッセージを受信した場合 31 | .match(Integer.class, msg -> log.info("received Integer message: {}", msg)) 32 | // String 型、Integer 型以外のメッセージを受信した場合 33 | .matchAny(msg -> log.info("received unknown message.")) 34 | .build(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a1_create/TicketSeller2.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a1_create; 2 | 3 | import akka.actor.AbstractActor; 4 | import akka.actor.Props; 5 | import akka.event.Logging; 6 | import akka.event.LoggingAdapter; 7 | import akka.japi.pf.ReceiveBuilder; 8 | 9 | // ReceiveBuilder の分割 10 | public class TicketSeller2 extends AbstractActor { 11 | static public Props props() { 12 | return Props.create(TicketSeller2.class, () -> new TicketSeller2()); 13 | } 14 | 15 | /** 注文メッセージ */ 16 | public static class Order { 17 | public Order() { 18 | } 19 | } 20 | 21 | private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); 22 | 23 | public TicketSeller2() { 24 | } 25 | 26 | @Override 27 | public Receive createReceive() { 28 | ReceiveBuilder builder = ReceiveBuilder.create(); 29 | 30 | // String 型の場合 31 | builder.match(String.class, msg -> { 32 | log.info("received String message: {}", msg); 33 | }); 34 | 35 | // Integer 型の場合 36 | builder.match(Integer.class, msg -> { 37 | log.info("received Integer message: {}", msg); 38 | }); 39 | 40 | // それ以外の場合 41 | builder.matchAny(msg -> log.info("received unknown message")); 42 | 43 | // build()メソッドによりReceive 型を返す 44 | return builder.build(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a1_create/TicketSeller3.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a1_create; 2 | 3 | import akka.actor.AbstractActor; 4 | import akka.actor.Props; 5 | import akka.event.Logging; 6 | import akka.event.LoggingAdapter; 7 | 8 | // 振る舞いを別メソッドに定義 9 | public class TicketSeller3 extends AbstractActor { 10 | static public Props props() { 11 | return Props.create(TicketSeller3.class, () -> new TicketSeller3()); 12 | } 13 | 14 | /** 注文メッセージ */ 15 | public static class Order { 16 | public Order() { 17 | } 18 | } 19 | 20 | private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); 21 | 22 | public TicketSeller3() { 23 | } 24 | 25 | @Override 26 | public Receive createReceive() { 27 | return receiveBuilder() 28 | .match(String.class, this::receiveString) 29 | .match(Integer.class, this::receiveInteger) 30 | .matchAny(this::receiveAny) 31 | .build(); 32 | } 33 | 34 | /** String 型の場合 */ 35 | private void receiveString(String msg) { 36 | log.info("received String message: {}", msg); 37 | } 38 | 39 | /** Integer 型の場合 */ 40 | private void receiveInteger(Integer msg) { 41 | log.info("received Integer message: {}", msg); 42 | } 43 | 44 | /** それ以外の場合 */ 45 | private void receiveAny(Object msg) { 46 | log.info("received unknown message"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a2_send1_tell/BoxOffice.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a2_send1_tell; 2 | 3 | import akka.actor.AbstractActor; 4 | import akka.actor.ActorRef; 5 | import akka.actor.Props; 6 | import akka.event.Logging; 7 | import akka.event.LoggingAdapter; 8 | 9 | // メッセージプロトコルの定義 10 | public class BoxOffice extends AbstractActor { 11 | static public Props props() { 12 | return Props.create(BoxOffice.class, () -> new BoxOffice()); 13 | } 14 | 15 | /** 初期化メッセージ */ 16 | public static class Initialize { 17 | public Initialize() { 18 | } 19 | } 20 | 21 | /** シャットダウンメッセージ */ 22 | public static class Shutdown { 23 | public Shutdown() { 24 | } 25 | } 26 | 27 | /** 注文メッセージ */ 28 | public static class Order { 29 | public Order() { 30 | } 31 | } 32 | 33 | /** 注文完了メッセージ */ 34 | public static class OrderCompleted { 35 | private final String message; 36 | 37 | public OrderCompleted(String message) { 38 | this.message = message; 39 | } 40 | 41 | public String getMessage() { 42 | return message; 43 | } 44 | } 45 | 46 | private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); 47 | 48 | public BoxOffice() { 49 | } 50 | 51 | ActorRef ticketSeller = getContext().actorOf(TicketSeller.props(), "ticketSeller"); 52 | 53 | @Override 54 | public Receive createReceive() { 55 | return receiveBuilder() 56 | // 初期化メッセージを受信 57 | .match(Initialize.class, initialize -> log.info("starting go ticks")) 58 | // 注文メッセージを受信 59 | .match(Order.class, order -> { 60 | ticketSeller.tell(new TicketSeller.Order("RHCP", 2), getSelf()); 61 | }) 62 | // シャットダウンメッセージを受信 63 | .match(Shutdown.class, shutdown -> { 64 | log.info("terminating go ticks"); 65 | getContext().getSystem().terminate(); 66 | }) 67 | // 注文完了メッセージを受信 68 | .match(OrderCompleted.class, result -> log.info("result: {}", result.getMessage())) 69 | .build(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a2_send1_tell/Main.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a2_send1_tell; 2 | 3 | import akka.actor.ActorRef; 4 | import akka.actor.ActorSystem; 5 | import com.typesafe.config.ConfigFactory; 6 | import scala.concurrent.duration.Duration; 7 | import scala.concurrent.Await; 8 | import com.goticks.a2_send1_tell.BoxOffice.*; 9 | 10 | class Main { 11 | public static void main(String args[]) throws Exception { 12 | 13 | final ActorSystem system = ActorSystem.create("main", ConfigFactory.load("goticks")); 14 | final ActorRef boxOffice = system.actorOf(BoxOffice.props(), "boxOffice"); 15 | 16 | boxOffice.tell(new Initialize(), ActorRef.noSender()); 17 | boxOffice.tell(new Order(), ActorRef.noSender()); 18 | 19 | // 2秒後にシャットダウン 20 | system.scheduler().scheduleOnce(Duration.create(2, "seconds"), boxOffice, new BoxOffice.Shutdown(), system.dispatcher(), ActorRef.noSender()); 21 | Await.result(system.whenTerminated(), Duration.Inf()); 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a2_send1_tell/TicketSeller.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a2_send1_tell; 2 | 3 | import akka.actor.AbstractActor; 4 | import akka.actor.Props; 5 | import akka.event.Logging; 6 | import akka.event.LoggingAdapter; 7 | 8 | // チケット販売員 9 | class TicketSeller extends AbstractActor { 10 | static public Props props() { 11 | return Props.create(TicketSeller.class, () -> new TicketSeller()); 12 | } 13 | 14 | /** 注文メッセージ */ 15 | public static class Order { 16 | private final String event; 17 | private final int nrTickets; 18 | 19 | public Order(String event, int nrTickets) { 20 | this.event = event; 21 | this.nrTickets = nrTickets; 22 | } 23 | 24 | public String getEvent() { 25 | return event; 26 | } 27 | 28 | public int getNrTickets() { 29 | return nrTickets; 30 | } 31 | } 32 | 33 | private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); 34 | 35 | public TicketSeller() { 36 | } 37 | 38 | @Override 39 | public Receive createReceive() { 40 | return receiveBuilder() 41 | .match(Order.class, order -> { 42 | // 注文を受けたときの振る舞い 43 | log.info("your order has been completed. (product: {}, nrTickets: {})", order.getEvent(), order.getNrTickets()); 44 | // 送信元に注文処理が完了したことを返す 45 | getSender().tell(new BoxOffice.OrderCompleted("OK"), getSender()); 46 | }) 47 | .matchAny(c -> log.info("received unknown message.")) // 想定外のメッセージを受信した場合 48 | .build(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a2_send2_ask/Main.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a2_send2_ask; 2 | 3 | import akka.actor.ActorRef; 4 | import akka.actor.ActorSystem; 5 | import com.typesafe.config.ConfigFactory; 6 | import scala.concurrent.duration.Duration; 7 | import scala.concurrent.Await; 8 | import com.goticks.a2_send2_ask.BoxOffice.*; 9 | 10 | class Main { 11 | public static void main(String args[]) throws Exception { 12 | 13 | final ActorSystem system = ActorSystem.create("main", ConfigFactory.load("goticks")); 14 | final ActorRef boxOffice = system.actorOf(BoxOffice.props(), "boxOffice"); 15 | 16 | boxOffice.tell(new Initialize(), ActorRef.noSender()); 17 | boxOffice.tell(new Order(), ActorRef.noSender()); 18 | boxOffice.tell(new Orders(), ActorRef.noSender()); 19 | 20 | // 20秒後にシャットダウン 21 | system.scheduler().scheduleOnce(Duration.create(5, "seconds"), boxOffice, new BoxOffice.Shutdown(), system.dispatcher(), null); 22 | Await.result(system.whenTerminated(), Duration.Inf()); 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a2_send2_ask/MusicSeller.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a2_send2_ask; 2 | 3 | 4 | import akka.actor.AbstractActor; 5 | import akka.actor.Props; 6 | import akka.event.Logging; 7 | import akka.event.LoggingAdapter; 8 | 9 | /** 音楽チケット担当 */ 10 | class MusicSeller extends AbstractActor { 11 | static public Props props(int offset) { 12 | return Props.create(MusicSeller.class, () -> new MusicSeller(offset)); 13 | } 14 | 15 | public static class RequestTicket { 16 | private final int nrTickets; 17 | 18 | public RequestTicket(int nrTickets) { 19 | this.nrTickets = nrTickets; 20 | } 21 | 22 | public int getNrTickets() { 23 | return nrTickets; 24 | } 25 | } 26 | 27 | private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); 28 | 29 | /** チケット残数 */ 30 | private int rest; 31 | 32 | public MusicSeller(int offset) { 33 | this.rest = offset; 34 | } 35 | 36 | 37 | @Override 38 | public Receive createReceive() { 39 | return receiveBuilder() 40 | .match(RequestTicket.class, order -> { 41 | rest -= order.getNrTickets(); // 受信した注文数をマイナス 42 | log.info("order:{}, rest:{}", order.getNrTickets(), rest); 43 | getSender().tell(new TicketSeller.OrderCompleted( 44 | "I'm a charge of Music events. received your order!"), getSelf()); 45 | }) 46 | .build(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a2_send2_ask/SportsSeller.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a2_send2_ask; 2 | 3 | 4 | import akka.actor.AbstractActor; 5 | import akka.actor.Props; 6 | import akka.event.Logging; 7 | import akka.event.LoggingAdapter; 8 | 9 | /** スポーツチケット担当 */ 10 | class SportsSeller extends AbstractActor { 11 | static public Props props(int offset) { 12 | return Props.create(SportsSeller.class, () -> new SportsSeller(offset)); 13 | } 14 | 15 | public static class RequestTicket { 16 | private final int nrTickets; 17 | 18 | public RequestTicket(int nrTickets) { 19 | this.nrTickets = nrTickets; 20 | } 21 | 22 | public int getNrTickets() { 23 | return nrTickets; 24 | } 25 | } 26 | 27 | private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); 28 | 29 | /** チケット残数 */ 30 | private int rest; 31 | 32 | public SportsSeller(int offset) { 33 | this.rest = offset; 34 | } 35 | 36 | 37 | @Override 38 | public Receive createReceive() { 39 | return receiveBuilder() 40 | .match(RequestTicket.class, order -> { 41 | rest -= order.getNrTickets(); // 受信した注文数をマイナス 42 | log.info("order:{}, rest:{}", order.getNrTickets(), rest); 43 | getSender().tell(new TicketSeller.OrderCompleted( 44 | "I'm a charge of Sports events. received your order!"), getSelf()); 45 | }) 46 | .build(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a3_supervisor/Main.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a3_supervisor; 2 | 3 | import akka.actor.ActorRef; 4 | import akka.actor.ActorSystem; 5 | import com.typesafe.config.ConfigFactory; 6 | import scala.concurrent.duration.Duration; 7 | import scala.concurrent.Await; 8 | import com.goticks.a3_supervisor.Shop.*; 9 | 10 | class Main { 11 | public static void main(String args[]) throws Exception { 12 | 13 | final ActorSystem system = ActorSystem.create("main", ConfigFactory.load("goticks")); 14 | final ActorRef shop = system.actorOf(Shop.props(), "shop"); 15 | 16 | // 初期化 17 | shop.tell(new Initialize(), ActorRef.noSender()); 18 | 19 | // 顧客からの注文 20 | shop.tell(new Order(new Sports(), 2), ActorRef.noSender()); 21 | shop.tell(new Order(new Music(), 30), ActorRef.noSender()); 22 | shop.tell(new Order(new Sports(), 1), ActorRef.noSender()); 23 | shop.tell(new Order(new Music(), 2), ActorRef.noSender()); 24 | 25 | // 2秒間隔をあける 26 | Thread.sleep(2000); 27 | 28 | // SportsSellerを停止させる 29 | shop.tell("killSports", ActorRef.noSender()); 30 | 31 | // 2秒間隔をあける 32 | Thread.sleep(2000); 33 | 34 | shop.tell(new Order(new Sports(), 1), ActorRef.noSender()); 35 | shop.tell(new Order(new Music(), 2), ActorRef.noSender()); 36 | shop.tell(new Order(new Music(), 3), ActorRef.noSender()); 37 | shop.tell(new Order(new Music(), 4), ActorRef.noSender()); 38 | 39 | // 10秒後にシャットダウン 40 | system.scheduler().scheduleOnce(Duration.create(10, "seconds"), shop, new Shop.Shutdown(), system.dispatcher(), ActorRef.noSender()); 41 | Await.result(system.whenTerminated(), Duration.Inf()); 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a3_supervisor/MusicSeller.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a3_supervisor; 2 | 3 | 4 | import akka.actor.AbstractActor; 5 | import akka.actor.Props; 6 | import akka.event.Logging; 7 | import akka.event.LoggingAdapter; 8 | 9 | /** 音楽チケット担当 */ 10 | class MusicSeller extends AbstractActor { 11 | static public Props props(int offset) { 12 | return Props.create(MusicSeller.class, () -> new MusicSeller(offset)); 13 | } 14 | 15 | /** チケットのリクエスト・メッセージ */ 16 | public static class RequestTicket { 17 | private final int nrTickets; 18 | 19 | public RequestTicket(int nrTickets) { 20 | this.nrTickets = nrTickets; 21 | } 22 | 23 | public int getNrTickets() { 24 | return nrTickets; 25 | } 26 | } 27 | 28 | private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); 29 | 30 | /** チケット残数 */ 31 | private int rest; 32 | 33 | public MusicSeller(int offset) { 34 | this.rest = offset; 35 | } 36 | 37 | @Override 38 | public Receive createReceive() { 39 | return receiveBuilder() 40 | .match(RequestTicket.class, order -> { 41 | log.info("order:{}, rest:{}", order.getNrTickets(), rest - order.nrTickets); 42 | if (rest < order.getNrTickets()) 43 | throw new TicketSeller.ExceededLimitException("no tickets."); 44 | rest -= order.getNrTickets(); // 受信した注文数をマイナス 45 | getSender().tell(new BoxOffice.OrderCompleted( 46 | "I'm a charge of Music events. received your order!"), getSelf()); 47 | }) 48 | .build(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a3_supervisor/SportsSeller.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a3_supervisor; 2 | 3 | 4 | import akka.actor.AbstractActor; 5 | import akka.actor.Props; 6 | import akka.event.Logging; 7 | import akka.event.LoggingAdapter; 8 | 9 | /** スポーツチケット担当 */ 10 | class SportsSeller extends AbstractActor { 11 | static public Props props(int offset) { 12 | return Props.create(SportsSeller.class, () -> new SportsSeller(offset)); 13 | } 14 | 15 | /** チケットのリクエスト・メッセージ */ 16 | public static class RequestTicket { 17 | private final int nrTickets; 18 | 19 | public RequestTicket(int nrTickets) { 20 | this.nrTickets = nrTickets; 21 | } 22 | 23 | public int getNrTickets() { 24 | return nrTickets; 25 | } 26 | } 27 | 28 | private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); 29 | 30 | /** チケット残数 */ 31 | private int rest; 32 | 33 | public SportsSeller(int offset) { 34 | this.rest = offset; 35 | } 36 | 37 | @Override 38 | public Receive createReceive() { 39 | return receiveBuilder() 40 | .match(RequestTicket.class, order -> { 41 | rest -= order.getNrTickets(); // 受信した注文数をマイナス 42 | log.info("order:{}, rest:{}", order.getNrTickets(), rest); 43 | getSender().tell(new BoxOffice.OrderCompleted( 44 | "I'm a charge of Sports events. received your order!"), getSelf()); 45 | }) 46 | .build(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a4_become1/Main.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a4_become1; 2 | 3 | 4 | import akka.actor.ActorRef; 5 | import akka.actor.ActorSystem; 6 | import com.typesafe.config.ConfigFactory; 7 | import scala.concurrent.duration.Duration; 8 | import scala.concurrent.Await; 9 | 10 | class Main { 11 | public static void main(String args[]) throws Exception { 12 | 13 | final ActorSystem system = ActorSystem.create("main", ConfigFactory.load("goticks")); 14 | final ActorRef boxOffice = system.actorOf(BoxOffice.props(), "boxOffice"); 15 | 16 | boxOffice.tell(new BoxOffice.Initialize(), ActorRef.noSender()); 17 | 18 | 19 | // イベントは「音楽」でオープン 20 | boxOffice.tell(new BoxOffice.Open(new BoxOffice.EventType("Music")), ActorRef.noSender()); 21 | 22 | // 5秒後にクローズ 23 | system.scheduler().scheduleOnce(Duration.create(5, "seconds"), boxOffice, new BoxOffice.Close(), system.dispatcher(), ActorRef.noSender()); 24 | // 10秒後にイベントを「スポーツ」でオープン 25 | system.scheduler().scheduleOnce(Duration.create(10, "seconds"), boxOffice, new BoxOffice.Open(new BoxOffice.EventType("Sports")), system.dispatcher(), ActorRef.noSender()); 26 | 27 | // 15秒後にシャットダウン 28 | system.scheduler().scheduleOnce(Duration.create(15, "seconds"), boxOffice, new BoxOffice.Shutdown(), system.dispatcher(), ActorRef.noSender()); 29 | 30 | // 3秒毎にチケットを注文 31 | boxOffice.tell(new BoxOffice.Order("RHCP", 4), ActorRef.noSender()); 32 | Thread.sleep(3000); 33 | boxOffice.tell(new BoxOffice.Order("RHCP", 1), ActorRef.noSender()); 34 | Thread.sleep(3000); 35 | boxOffice.tell(new BoxOffice.Order("RHCP", 2), ActorRef.noSender()); 36 | Thread.sleep(3000); 37 | boxOffice.tell(new BoxOffice.Order("RHCP", 3), ActorRef.noSender()); 38 | Thread.sleep(3000); 39 | boxOffice.tell(new BoxOffice.Order("RHCP", 5), ActorRef.noSender()); 40 | 41 | Await.result(system.whenTerminated(), Duration.Inf()); 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter-java/src/main/java/com/goticks/a4_become2_fsm/Main.java: -------------------------------------------------------------------------------- 1 | package com.goticks.a4_become2_fsm; 2 | 3 | 4 | import akka.actor.ActorRef; 5 | import akka.actor.ActorSystem; 6 | import com.typesafe.config.ConfigFactory; 7 | import scala.concurrent.duration.Duration; 8 | import scala.concurrent.Await; 9 | import com.goticks.a4_become2_fsm.BoxOffice.*; 10 | 11 | class Main { 12 | public static void main(String args[]) throws Exception { 13 | 14 | final ActorSystem system = ActorSystem.create("main", ConfigFactory.load("goticks")); 15 | final ActorRef boxOffice = system.actorOf(BoxOffice.props(), "boxOffice"); 16 | 17 | boxOffice.tell(new Initialize(), ActorRef.noSender()); 18 | 19 | // 5秒後にクローズ 20 | system.scheduler().scheduleOnce(Duration.create(5, "seconds"), boxOffice, new Close(), system.dispatcher(), ActorRef.noSender()); 21 | // 10秒後にイベントをオープン 22 | system.scheduler().scheduleOnce(Duration.create(10, "seconds"), boxOffice, new Open(), system.dispatcher(), ActorRef.noSender()); 23 | 24 | // 15秒後にクローズ 25 | system.scheduler().scheduleOnce(Duration.create(15, "seconds"), boxOffice, new Close(), system.dispatcher(), ActorRef.noSender()); 26 | 27 | // 20秒後にシャットダウン 28 | system.scheduler().scheduleOnce(Duration.create(20, "seconds"), boxOffice, new Shutdown(), system.dispatcher(), ActorRef.noSender()); 29 | 30 | // 3秒毎にチケットを注文 31 | boxOffice.tell(new Order("RHCP", 4), ActorRef.noSender()); 32 | Thread.sleep(3000); 33 | boxOffice.tell(new Order("RHCP", 1), ActorRef.noSender()); 34 | Thread.sleep(3000); 35 | boxOffice.tell(new Order("RHCP", 2), ActorRef.noSender()); 36 | Thread.sleep(3000); 37 | boxOffice.tell(new Order("RHCP", 3), ActorRef.noSender()); 38 | Thread.sleep(3000); 39 | boxOffice.tell(new Order("RHCP", 5), ActorRef.noSender()); 40 | 41 | Await.result(system.whenTerminated(), Duration.Inf()); 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter-java/src/main/resources/goticks.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loggers = ["akka.event.slf4j.Slf4jLogger"] 3 | loglevel = DEBUG 4 | logging-filter = "akka.event.slf4j.Slf4jLoggingFilter" 5 | actor { 6 | debug { 7 | # enable DEBUG logging of unhandled messages 8 | unhandled = on 9 | } 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /chapter-java/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [%date{HH:mm:ss.SSS}] %logger - %message%n%xException{10} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /chapter-looking-ahead/build.sbt: -------------------------------------------------------------------------------- 1 | name := "next" 2 | 3 | version := "1.0" 4 | 5 | organization := "com.manning" 6 | 7 | resolvers ++= Seq("Typesafe Snapshots" at "http://repo.akka.io/snapshots/") 8 | 9 | parallelExecution in Test := false 10 | 11 | fork := true 12 | 13 | libraryDependencies ++= { 14 | val akkaVersion = "2.5.4" 15 | Seq( 16 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 17 | "com.typesafe.akka" %% "akka-typed" % akkaVersion, 18 | "com.typesafe.akka" %% "akka-persistence" % akkaVersion, 19 | "commons-io" % "commons-io" % "2.4", 20 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /chapter-looking-ahead/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.15 2 | -------------------------------------------------------------------------------- /chapter-looking-ahead/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Classpaths.typesafeResolver 2 | -------------------------------------------------------------------------------- /chapter-looking-ahead/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-looking-ahead/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka.persistence.journal.plugin = "akka.persistence.journal.inmem" 2 | akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local" 3 | -------------------------------------------------------------------------------- /chapter-looking-ahead/src/main/scala/aia/next/Shopper.scala: -------------------------------------------------------------------------------- 1 | package aia.next 2 | 3 | 4 | import akka.actor._ 5 | 6 | object Shopper { 7 | 8 | trait Command { 9 | def shopperId: Long 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /chapter-looking-ahead/src/main/scala/aia/next/TypedBasket.scala: -------------------------------------------------------------------------------- 1 | package aia.next 2 | 3 | import akka.typed._ 4 | import akka.typed.scaladsl.Actor 5 | 6 | object TypedBasket { 7 | sealed trait Command { 8 | def shopperId: Long 9 | } 10 | 11 | final case class GetItems(shopperId: Long, 12 | replyTo: ActorRef[Items]) extends Command 13 | final case class Add(item: Item, shopperId: Long) extends Command 14 | 15 | // a simplified version of Items and Item 16 | case class Items(list: Vector[Item]= Vector.empty[Item]) 17 | case class Item(productId: String, number: Int, unitPrice: BigDecimal) 18 | 19 | def basketBehavior(items: Items = Items()): Behavior[Command] = 20 | Actor.immutable[Command] { (ctx, msg) => 21 | msg match { 22 | case GetItems(productId, replyTo) => 23 | replyTo ! items 24 | basketBehavior(items) 25 | case Add(item, productId) => 26 | basketBehavior(Items(items.list :+ item)) 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /chapter-looking-ahead/src/test/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka.persistence.journal.leveldb.native = off 2 | 3 | akka { 4 | loggers = ["akka.testkit.TestEventListener"] 5 | 6 | actor { 7 | provider = "akka.actor.LocalActorRefProvider" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter-looking-ahead/src/test/scala/aia/next/BasketSpec.scala: -------------------------------------------------------------------------------- 1 | package aia.next 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import akka.actor._ 6 | import akka.testkit._ 7 | import org.scalatest._ 8 | 9 | class BasketSpec extends PersistenceSpec(ActorSystem("test")) 10 | with PersistenceCleanup { 11 | 12 | val shopperId = 5L 13 | val macbookPro = Item("Apple Macbook Pro", 1, BigDecimal(2499.99)) 14 | val displays = Item("4K Display", 3, BigDecimal(2499.99)) 15 | 16 | "The basket" should { 17 | "return the items" in { 18 | val basket = system.actorOf(Basket.props, Basket.name(shopperId)) 19 | basket ! Basket.Add(macbookPro, shopperId) 20 | basket ! Basket.Add(displays, shopperId) 21 | 22 | basket ! Basket.GetItems(shopperId) 23 | //basket ! Basket.GetItems 24 | expectMsg(Items(macbookPro, displays)) 25 | killActors(basket) 26 | } 27 | 28 | "return the items in a typesafe way" in { 29 | import akka.typed._ 30 | import akka.typed.scaladsl.Actor._ 31 | import akka.typed.scaladsl.AskPattern._ 32 | import scala.concurrent.Future 33 | import scala.concurrent.duration._ 34 | import scala.concurrent.Await 35 | 36 | implicit val timeout = akka.util.Timeout(1 second) 37 | 38 | val macbookPro = 39 | TypedBasket.Item("Apple Macbook Pro", 1, BigDecimal(2499.99)) 40 | val displays = 41 | TypedBasket.Item("4K Display", 3, BigDecimal(2499.99)) 42 | 43 | val sys: ActorSystem[TypedBasket.Command] = 44 | ActorSystem(TypedBasket.basketBehavior(), "typed-basket") 45 | sys ! TypedBasket.Add(macbookPro, shopperId) 46 | sys ! TypedBasket.Add(displays, shopperId) 47 | 48 | implicit def scheduler = sys.scheduler 49 | val items: Future[TypedBasket.Items] = 50 | sys ? (TypedBasket.GetItems(shopperId, _)) 51 | 52 | val res = Await.result(items, 10 seconds) 53 | res should equal(TypedBasket.Items(Vector(macbookPro, displays))) 54 | //sys ? Basket.GetItems 55 | sys.terminate() 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /chapter-looking-ahead/src/test/scala/aia/next/PersistenceSpec.scala: -------------------------------------------------------------------------------- 1 | package akka.testkit 2 | 3 | 4 | import java.io.File 5 | import com.typesafe.config._ 6 | 7 | import scala.util._ 8 | 9 | import akka.actor._ 10 | import akka.persistence._ 11 | import org.scalatest._ 12 | 13 | import org.apache.commons.io.FileUtils 14 | 15 | abstract class PersistenceSpec(system: ActorSystem) extends TestKit(system) 16 | with ImplicitSender 17 | with WordSpecLike 18 | with Matchers 19 | with BeforeAndAfterAll 20 | with PersistenceCleanup { 21 | 22 | def this(name: String, config: Config) = this(ActorSystem(name, config)) 23 | override protected def beforeAll() = deleteStorageLocations() 24 | 25 | override protected def afterAll() = { 26 | deleteStorageLocations() 27 | TestKit.shutdownActorSystem(system) 28 | } 29 | 30 | def killActors(actors: ActorRef*) = { 31 | actors.foreach { actor => 32 | watch(actor) 33 | system.stop(actor) 34 | expectTerminated(actor) 35 | Thread.sleep(1000) // the actor name is not unique intermittently on travis when creating it again after killActors, this is ducktape. 36 | } 37 | } 38 | } 39 | 40 | trait PersistenceCleanup { 41 | def system: ActorSystem 42 | 43 | val storageLocations = List( 44 | "akka.persistence.journal.leveldb.dir", 45 | "akka.persistence.journal.leveldb-shared.store.dir", 46 | "akka.persistence.snapshot-store.local.dir").map(s => new File(system.settings.config.getString(s))) 47 | 48 | def deleteStorageLocations(): Unit = { 49 | storageLocations.foreach(dir => Try(FileUtils.deleteDirectory(dir))) 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /chapter-perf/README.md: -------------------------------------------------------------------------------- 1 | Performance tests 2 | ================= 3 | 4 | These performance test are dependent on the machine the test are run. Some test are designed to fail, due to 5 | performance problems, as described in the book. These test are: 6 | * aia.performance.dispatcher.DispatcherInitTest 7 | * aia.performance.dispatcher.DispatcherPinnedTest 8 | * aia.performance.dispatcher.DispatcherSeparateTest 9 | * aia.performance.dispatcher.DispatcherThroughputTest 10 | 11 | But it is possible that other tests fail too. Especially when running all the test at once. 12 | These are designed to stress the system its running on. Therefore these test can fail on some systems 13 | and succeed on others. These test are more to get the feeling for the effects the different configurations 14 | can have on the performance of an application 15 | 16 | 17 | -------------------------------------------------------------------------------- /chapter-perf/build.sbt: -------------------------------------------------------------------------------- 1 | name := "performance" 2 | 3 | version := "1.0" 4 | 5 | organization := "com.manning" 6 | 7 | libraryDependencies ++= { 8 | val akkaVersion = "2.5.4" 9 | Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 13 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /chapter-perf/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-perf/src/main/scala/aia/performance/SimulatorSystem.scala: -------------------------------------------------------------------------------- 1 | package aia.performance 2 | 3 | import akka.actor.{ ActorRef, Actor } 4 | import concurrent.duration._ 5 | import monitor.StatisticsSummary 6 | 7 | case class SystemMessage(start: Long = 0, duration: Long = 0, id: String = "") 8 | 9 | class ProcessRequest(serviceTime: Duration, next: ActorRef) extends Actor { 10 | def receive = { 11 | case msg: SystemMessage => { 12 | //simulate processing 13 | Thread.sleep(serviceTime.toMillis) 14 | next ! msg.copy(duration = System.currentTimeMillis() - msg.start, id = self.path.toString) 15 | //println("%s: Send message".format(self.toString())) 16 | } 17 | } 18 | } 19 | 20 | class ProcessCPURequest(serviceTime: Duration, next: ActorRef) extends Actor { 21 | def receive = { 22 | case msg: SystemMessage => { 23 | //simulate processing by doing some calculations 24 | var tmp = math.Pi 25 | var tmp2 = math.toRadians(tmp) 26 | val start = System.currentTimeMillis() 27 | 28 | do { 29 | for (i <- 1 until 10000) { 30 | tmp = tmp * 42589 / (tmp2 * 37) * 42589 31 | tmp2 = tmp2 * math.toDegrees(tmp) 32 | } 33 | } while (System.currentTimeMillis() - start < serviceTime.toMillis) 34 | 35 | next ! msg.copy(duration = System.currentTimeMillis() - msg.start, id = self.path.toString) 36 | } 37 | } 38 | } 39 | 40 | class PrintMsg extends Actor { 41 | var receivedStats: Seq[List[StatisticsSummary]] = Seq() 42 | def receive = { 43 | case "print" => { 44 | println("!!!!!!PRINT!!!!!!!! nr=%d".format(receivedStats.size)) 45 | 46 | receivedStats.foreach(msg => { 47 | msg.filter(_.actorId.contains("Entry")).foreach(entry => 48 | println("ENTRY: maxQueue=%d, utilization %02f: %s".format(entry.maxQueueLength, entry.utilization, entry.toString))) 49 | }) 50 | 51 | receivedStats.foreach(msg => { 52 | println("Received %s".format(msg.toString)) 53 | }) 54 | } 55 | case msg: List[StatisticsSummary] => { 56 | receivedStats = receivedStats :+ msg 57 | //simulate processing 58 | println("Received %s".format(msg.toString)) 59 | } 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /chapter-perf/src/main/scala/aia/performance/monitor/MonitorActor.scala: -------------------------------------------------------------------------------- 1 | package aia.performance.monitor 2 | 3 | import akka.actor.Actor 4 | 5 | case class ActorStatistics(receiver: String, 6 | sender: String, 7 | entryTime: Long, 8 | exitTime: Long) 9 | 10 | 11 | trait MonitorActor extends Actor { 12 | 13 | abstract override def receive = { 14 | case m: Any => { 15 | val start = System.currentTimeMillis() 16 | super.receive(m) 17 | val end = System.currentTimeMillis() 18 | 19 | val stat = ActorStatistics( 20 | self.toString(), 21 | sender.toString(), 22 | start, 23 | end) 24 | context.system.eventStream.publish(stat) 25 | } 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /chapter-perf/src/test/resources/monitor/mailbox.conf: -------------------------------------------------------------------------------- 1 | 2 | akka.actor.default-mailbox { 3 | mailbox-type = aia.performance.monitor.MonitorMailboxType 4 | } 5 | 6 | my-dispatcher { 7 | mailbox-type = aia.performance.monitor.MonitorMailboxType 8 | } -------------------------------------------------------------------------------- /chapter-perf/src/test/resources/performance/dispatcher.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | actor { 3 | default-dispatcher { 4 | mailbox-type = "aia.performance.monitor.MonitorMailboxType" 5 | } 6 | } 7 | } 8 | 9 | my-dispatcher { 10 | mailbox-type = aia.performance.monitor.MonitorMailboxType 11 | } 12 | 13 | my-dispatcher2 { 14 | } 15 | 16 | my-pinned-dispatcher { 17 | executor = "thread-pool-executor" 18 | type = PinnedDispatcher 19 | mailbox-type = aia.performance.monitor.MonitorMailboxType 20 | } 21 | 22 | myMultiThread-dispatcher { 23 | # executor = "thread-pool-executor" 24 | executor = "fork-join-executor" 25 | fork-join-executor { 26 | parallelism-min = 100 27 | # Max number of threads to cap factor-based parallelism number to 28 | parallelism-factor = 3.0 29 | parallelism-max = 150 30 | } 31 | thread-pool-executor { 32 | # Max number of threads to cap factor-based number to 33 | core-pool-size-max = 150 34 | core-pool-size-factor = 75.0 35 | # Max number of threads to cap factor-based max number to 36 | # (if using a bounded task queue) 37 | max-pool-size-max = 150 38 | } 39 | mailbox-type = aia.performance.monitor.MonitorMailboxType 40 | } 41 | 42 | throughput-dispatcher { 43 | executor = "fork-join-executor" 44 | throughput = 1000 45 | # Throughput deadline for Dispatcher, set to 0 or negative for no deadline 46 | #throughput-deadline-time = 900ms 47 | 48 | mailbox-type = aia.performance.monitor.MonitorMailboxType 49 | } 50 | 51 | my-thread-dispatcher { 52 | executor = "thread-pool-executor" 53 | thread-pool-executor { 54 | core-pool-size-min = 1 55 | core-pool-size-max = 1 56 | task-queue-size = 5 57 | 58 | # Max number of threads to cap factor-based number to 59 | # Max number of threads to cap factor-based max number to 60 | # (if using a bounded task queue) 61 | max-pool-size-min = 150 62 | max-pool-size-max = 150 63 | } 64 | } -------------------------------------------------------------------------------- /chapter-perf/src/test/resources/performance/through.conf: -------------------------------------------------------------------------------- 1 | my-pinned-dispatcher { 2 | executor = "thread-pool-executor" 3 | type = PinnedDispatcher 4 | mailbox-type = aia.performance.monitor.MonitorMailboxType 5 | } 6 | 7 | my-dispatcher { 8 | fork-join-executor { 9 | parallelism-min = 4 10 | # Max number of threads to cap factor-based parallelism number to 11 | parallelism-factor = 3.0 12 | parallelism-max = 4 13 | } 14 | 15 | throughput = 1 16 | # Throughput deadline for Dispatcher, 17 | # set to 0 or negative for no deadline 18 | #throughput-deadline-time = 900ms 19 | } -------------------------------------------------------------------------------- /chapter-perf/src/test/scala/aia/performance/monitor/MonitorActorTest.scala: -------------------------------------------------------------------------------- 1 | package aia.performance.monitor 2 | 3 | import org.scalatest.{WordSpecLike, BeforeAndAfterAll, MustMatchers} 4 | import akka.testkit.{ TestProbe, TestKit } 5 | import akka.actor.{ Props, ActorSystem } 6 | import concurrent.duration._ 7 | 8 | 9 | class MonitorActorTest extends TestKit(ActorSystem("MonitorActorTest")) 10 | with WordSpecLike 11 | with BeforeAndAfterAll 12 | with MustMatchers { 13 | 14 | "Actor" must { 15 | "send statistics" in { 16 | val statProbe = TestProbe() 17 | system.eventStream.subscribe( 18 | statProbe.ref, 19 | classOf[ActorStatistics]) 20 | val testActor = system.actorOf(Props( 21 | new ProcessTestActor(1.second) with MonitorActor), "monitorActor") 22 | statProbe.send(testActor, "message") 23 | statProbe.send(testActor, "message2") 24 | statProbe.send(testActor, "message3") 25 | 26 | val stat = statProbe.expectMsgType[ActorStatistics] 27 | println(stat) 28 | stat.exitTime - stat.entryTime must be(1000L +- 20) 29 | val stat2 = statProbe.expectMsgType[ActorStatistics] 30 | println(stat2) 31 | stat2.exitTime - stat2.entryTime must be(1000L +- 20) 32 | val stat3 = statProbe.expectMsgType[ActorStatistics] 33 | println(stat3) 34 | stat3.exitTime - stat3.entryTime must be(1000L +- 20) 35 | 36 | Thread.sleep(2000) 37 | system.stop(testActor) 38 | system.eventStream.unsubscribe(statProbe.ref) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /chapter-perf/src/test/scala/aia/performance/throughput/ThroughputCPUTest.scala: -------------------------------------------------------------------------------- 1 | package aia.performance.throughput 2 | 3 | import akka.testkit.TestProbe 4 | import akka.actor.{Props, ActorSystem} 5 | import org.scalatest.{WordSpecLike, BeforeAndAfterAll, MustMatchers} 6 | import akka.routing.RoundRobinPool 7 | import com.typesafe.config.ConfigFactory 8 | import aia.performance.{ProcessCPURequest, SystemMessage, ProcessRequest} 9 | import concurrent.duration._ 10 | 11 | 12 | class ThroughputCPUTest extends WordSpecLike 13 | with BeforeAndAfterAll 14 | with MustMatchers { 15 | 16 | val configuration = ConfigFactory.load("performance/through") 17 | implicit val system = ActorSystem("ThroughputTest", configuration) 18 | 19 | "System" must { 20 | "fails to with cpu" in { 21 | val nrWorkers = 40 22 | val nrMessages = nrWorkers * 40 23 | 24 | val end = TestProbe() 25 | val workers = system.actorOf( 26 | RoundRobinPool(nrWorkers).props( 27 | Props(new ProcessCPURequest(250 millis, end.ref)).withDispatcher("my-dispatcher")), 28 | "Workers-cpu") 29 | 30 | val startTime = System.currentTimeMillis() 31 | for (i <- 0 until nrMessages) { 32 | workers ! new SystemMessage(startTime, 0, "") 33 | } 34 | val msg = end.receiveN(n = nrMessages, max = 9000 seconds).asInstanceOf[Seq[SystemMessage]] 35 | val endTime = System.currentTimeMillis() 36 | val total = endTime - startTime 37 | println("total process time %d Average=%d".format(total, total / nrMessages)) 38 | val grouped = msg.groupBy(_.id) 39 | grouped.map { 40 | case (key, listMsg) => (key, listMsg.foldLeft(0L) { (m, x) => math.max(m, x.duration) }) 41 | }.foreach(println(_)) 42 | 43 | Thread.sleep(1000) 44 | 45 | system.stop(workers) 46 | /* throughput 5 47 | total process time 104094 Average=65 48 | */ 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /chapter-persistence/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.15 2 | -------------------------------------------------------------------------------- /chapter-persistence/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Classpaths.typesafeResolver 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0") 4 | 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.8") 6 | -------------------------------------------------------------------------------- /chapter-persistence/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | passivate-timeout = 5 seconds 2 | 3 | http { 4 | host = "0.0.0.0" 5 | port = 5000 6 | } 7 | 8 | akka { 9 | loggers = ["akka.event.slf4j.Slf4jLogger"] 10 | 11 | actor { 12 | provider = "akka.cluster.ClusterActorRefProvider" 13 | 14 | } 15 | 16 | remote { 17 | log-remote-lifecycle-events = off 18 | netty.tcp { 19 | hostname = "127.0.0.1" 20 | port = 2552 21 | } 22 | } 23 | 24 | cluster { 25 | seed-nodes = ["akka.tcp://shoppers@127.0.0.1:2552", "akka.tcp://shoppers@127.0.0.1:2553"] 26 | } 27 | 28 | persistence { 29 | journal { 30 | plugin = akka.persistence.journal.leveldb 31 | leveldb { 32 | dir = "target/persistence/journal" 33 | native = on 34 | } 35 | } 36 | snapshot-store { 37 | plugin = akka.persistence.snapshot-store.local 38 | local.dir = "target/persistence/snapshots" 39 | } 40 | } 41 | } 42 | 43 | // 44 | akka { 45 | actor { 46 | serializers { 47 | basket = "aia.persistence.BasketEventSerializer" // 48 | basketSnapshot = "aia.persistence.BasketSnapshotSerializer" // 49 | } 50 | serialization-bindings { // 51 | "aia.persistence.Basket$Event" = basket 52 | "aia.persistence.Basket$Snapshot" = basketSnapshot 53 | } 54 | } 55 | } 56 | // 57 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/LocalShoppers.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence 2 | 3 | import akka.actor._ 4 | 5 | object LocalShoppers { 6 | def props = Props(new LocalShoppers) 7 | def name = "local-shoppers" 8 | } 9 | 10 | class LocalShoppers extends Actor 11 | with ShopperLookup { 12 | def receive = forwardToShopper 13 | } 14 | 15 | trait ShopperLookup { 16 | implicit def context: ActorContext 17 | 18 | def forwardToShopper: Actor.Receive = { 19 | case cmd: Shopper.Command => 20 | context.child(Shopper.name(cmd.shopperId)) 21 | .fold(createAndForward(cmd, cmd.shopperId))(forwardCommand(cmd)) 22 | } 23 | 24 | def forwardCommand(cmd: Shopper.Command)(shopper: ActorRef) = 25 | shopper forward cmd 26 | 27 | def createAndForward(cmd: Shopper.Command, shopperId: Long) = { 28 | createShopper(shopperId) forward cmd 29 | } 30 | 31 | def createShopper(shopperId: Long) = 32 | context.actorOf(Shopper.props(shopperId), 33 | Shopper.name(shopperId)) 34 | } 35 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/PaymentHistory.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence 2 | 3 | import akka.actor._ 4 | import akka.persistence._ 5 | 6 | import akka.persistence.query.PersistenceQuery 7 | import akka.persistence.query.journal.leveldb.scaladsl.LeveldbReadJournal 8 | 9 | import akka.stream.ActorMaterializer 10 | import akka.stream.scaladsl.Sink 11 | 12 | object PaymentHistory { 13 | def props(shopperId: Long) = Props(new PaymentHistory(shopperId)) 14 | def name(shopperId: Long) = s"payment_history_${shopperId}" 15 | 16 | case object GetHistory 17 | 18 | case class History(items: List[Item] = Nil) { 19 | def paid(paidItems: List[Item]) = { 20 | History(paidItems ++ items) 21 | } 22 | } 23 | } 24 | 25 | class PaymentHistory(shopperId: Long) extends Actor 26 | with ActorLogging { 27 | import Basket._ 28 | import PaymentHistory._ 29 | 30 | val queries = PersistenceQuery(context.system).readJournalFor[LeveldbReadJournal]( 31 | LeveldbReadJournal.Identifier) 32 | implicit val materializer = ActorMaterializer() 33 | queries.eventsByPersistenceId(Wallet.name(shopperId)).runWith(Sink.actorRef(self, None)) 34 | 35 | var history = History() 36 | 37 | def receive = { 38 | case Wallet.Paid(items, _) => history = history.paid(items) 39 | case GetHistory => sender() ! history 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/Settings.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import akka.actor._ 6 | 7 | import com.typesafe.config.Config 8 | 9 | object Settings extends ExtensionKey[Settings] 10 | 11 | class Settings(config: Config) extends Extension { 12 | def this(system: ExtendedActorSystem) = this(system.settings.config) 13 | 14 | val passivateTimeout = Duration(config.getString("passivate-timeout")) 15 | object http { 16 | val host = config.getString("http.host") 17 | val port = config.getInt("http.port") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/Shopper.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence 2 | 3 | 4 | import akka.actor._ 5 | 6 | object Shopper { 7 | def props(shopperId: Long) = Props(new Shopper) 8 | def name(shopperId: Long) = shopperId.toString 9 | 10 | trait Command { 11 | def shopperId: Long 12 | } 13 | 14 | case class PayBasket(shopperId: Long) extends Command 15 | // for simplicity every shopper got 40k to spend. 16 | val cash = 40000 17 | } 18 | 19 | class Shopper extends Actor { 20 | import Shopper._ 21 | 22 | def shopperId = self.path.name.toLong 23 | 24 | val basket = context.actorOf(Basket.props, 25 | Basket.name(shopperId)) 26 | 27 | 28 | val wallet = context.actorOf(Wallet.props(shopperId, cash), 29 | Wallet.name(shopperId)) 30 | 31 | def receive = { 32 | case cmd: Basket.Command => basket forward cmd 33 | case cmd: Wallet.Command => wallet forward cmd 34 | 35 | case PayBasket(shopperId) => basket ! Basket.GetItems(shopperId) 36 | case Items(list) => wallet ! Wallet.Pay(list, shopperId) 37 | case paid: Wallet.Paid => 38 | basket ! Basket.Clear(paid.shopperId) 39 | context.system.eventStream.publish(paid) 40 | } 41 | } 42 | 43 | 44 | 45 | // alternative PayBasket handling: 46 | // issue: ask timeout 47 | // benefit: can report back to sender of final result. 48 | // 49 | // case PayBasket(shopperId) => 50 | // import scala.concurrent.duration._ 51 | // import context.dispatcher 52 | // import akka.pattern.ask 53 | // implicit val timeout = akka.util.Timeout(10 seconds) 54 | // for { 55 | // items <- basket.ask(Basket.GetItems(shopperId)).mapTo[Items] 56 | // paid <- wallet.ask(Wallet.Pay(items.list, shopperId)).mapTo[Wallet.Paid] 57 | // } yield { 58 | // basket ! Basket.Clear(shopperId) 59 | // } 60 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/ShoppersSingleton.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence 2 | 3 | 4 | import akka.actor._ 5 | import akka.cluster.singleton.ClusterSingletonManager 6 | import akka.cluster.singleton.ClusterSingletonManagerSettings 7 | import akka.cluster.singleton.ClusterSingletonProxy 8 | import akka.cluster.singleton.ClusterSingletonProxySettings 9 | import akka.persistence._ 10 | 11 | object ShoppersSingleton { 12 | def props = Props(new ShoppersSingleton) 13 | def name = "shoppers-singleton" 14 | } 15 | 16 | class ShoppersSingleton extends Actor { 17 | 18 | val singletonManager = context.system.actorOf( 19 | ClusterSingletonManager.props( 20 | Shoppers.props, 21 | PoisonPill, 22 | ClusterSingletonManagerSettings(context.system) 23 | .withRole(None) 24 | .withSingletonName(Shoppers.name) 25 | ) 26 | ) 27 | 28 | val shoppers = context.system.actorOf( 29 | ClusterSingletonProxy.props( 30 | singletonManager.path.child(Shoppers.name) 31 | .toStringWithoutAddress, 32 | ClusterSingletonProxySettings(context.system) 33 | .withRole(None) 34 | .withSingletonName("shoppers-proxy") 35 | ) 36 | ) 37 | 38 | def receive = { 39 | case command: Shopper.Command => shoppers forward command 40 | } 41 | } 42 | 43 | 44 | 45 | object Shoppers { 46 | def props = Props(new Shoppers) 47 | def name = "shoppers" 48 | 49 | sealed trait Event 50 | case class ShopperCreated(shopperId: Long) 51 | } 52 | 53 | class Shoppers extends PersistentActor 54 | with ShopperLookup { 55 | import Shoppers._ 56 | 57 | def persistenceId = "shoppers" 58 | 59 | def receiveCommand = forwardToShopper 60 | 61 | override def createAndForward( 62 | cmd: Shopper.Command, 63 | shopperId: Long 64 | ) = { 65 | val shopper = createShopper(shopperId) 66 | persistAsync(ShopperCreated(shopperId)) { _ => 67 | forwardCommand(cmd)(shopper) 68 | } 69 | } 70 | 71 | def receiveRecover = { 72 | case ShopperCreated(shopperId) => 73 | context.child(Shopper.name(shopperId)) 74 | .getOrElse(createShopper(shopperId)) 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/SingletonMain.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import akka.actor._ 6 | import akka.io.IO 7 | import akka.pattern.ask 8 | import akka.util.Timeout 9 | 10 | import aia.persistence.rest.ShoppersServiceSupport 11 | 12 | object SingletonMain extends App with ShoppersServiceSupport { 13 | implicit val system = ActorSystem("shoppers") 14 | val shoppers = system.actorOf(ShoppersSingleton.props, 15 | ShoppersSingleton.name) 16 | startService(shoppers) 17 | } 18 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/Wallet.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence 2 | 3 | import akka.actor._ 4 | import akka.persistence._ 5 | 6 | object Wallet { 7 | def props(shopperId: Long, cash: BigDecimal) = 8 | Props(new Wallet(shopperId, cash)) 9 | def name(shopperId: Long) = s"wallet_${shopperId}" 10 | 11 | sealed trait Command extends Shopper.Command 12 | case class Pay(items: List[Item], shopperId: Long) extends Command 13 | case class Check(shopperId: Long) extends Command 14 | case class SpentHowMuch(shopperId: Long) extends Command 15 | 16 | case class AmountSpent(amount: BigDecimal) 17 | case class NotEnoughCash(left: BigDecimal) 18 | case class Cash(left: BigDecimal) 19 | 20 | sealed trait Event 21 | case class Paid(list: List[Item], shopperId: Long) extends Event 22 | } 23 | 24 | class Wallet(shopperId: Long, cash: BigDecimal) extends PersistentActor 25 | with ActorLogging { 26 | import Wallet._ 27 | var amountSpent: BigDecimal = 0 28 | 29 | def persistenceId = s"${self.path.name}" 30 | 31 | def receiveCommand = { 32 | case Pay(items, _) => 33 | val totalSpent = addSpending(items) 34 | if(cash - totalSpent > 0) { 35 | persist(Paid(items, shopperId)) { paid => 36 | updateState(paid) 37 | sender() ! paid 38 | } 39 | } else { 40 | context.system.eventStream.publish(NotEnoughCash(cash - amountSpent)) 41 | } 42 | case Check(_) => sender() ! Cash(cash - amountSpent) 43 | case SpentHowMuch(_) => sender() ! AmountSpent(amountSpent) 44 | } 45 | 46 | def receiveRecover = { 47 | case event: Event => updateState(event) 48 | } 49 | 50 | private val updateState: (Event => Unit) = { 51 | case paidItems @ Paid(items, _) => amountSpent = addSpending(items) 52 | } 53 | 54 | private def addSpending(items: List[Item]) = 55 | amountSpent + items.foldLeft(BigDecimal(0)){ (total, item) => 56 | total + (item.unitPrice * item.number) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/calculator/CalculatorHistory.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence.calculator 2 | 3 | import akka.actor._ 4 | import akka.persistence._ 5 | 6 | import akka.persistence.query.PersistenceQuery 7 | import akka.persistence.query.journal.leveldb.scaladsl.LeveldbReadJournal 8 | 9 | import akka.stream.ActorMaterializer 10 | import akka.stream.scaladsl.Sink 11 | 12 | object CalculatorHistory { 13 | def props = Props(new CalculatorHistory) 14 | def name = "calculator-history" 15 | case object GetHistory 16 | case class History(added: Int = 0, subtracted: Int = 0, divided: Int = 0, multiplied: Int = 0) { 17 | def incrementAdded = copy(added = added + 1) 18 | def incrementSubtracted= copy(subtracted = subtracted + 1) 19 | def incrementDivided = copy(divided = divided + 1) 20 | def incrementMultiplied = copy(multiplied = multiplied + 1) 21 | } 22 | } 23 | 24 | class CalculatorHistory extends Actor { 25 | import Calculator._ 26 | import CalculatorHistory._ 27 | 28 | val queries = PersistenceQuery(context.system).readJournalFor[LeveldbReadJournal]( 29 | LeveldbReadJournal.Identifier) 30 | implicit val materializer = ActorMaterializer() 31 | queries.eventsByPersistenceId(Calculator.name).runWith(Sink.actorRef(self, None)) 32 | 33 | var history = History() 34 | 35 | def receive = { 36 | case event: Added => history = history.incrementAdded 37 | case event: Subtracted => history = history.incrementSubtracted 38 | case event: Divided => history = history.incrementDivided 39 | case event: Multiplied => history = history.incrementMultiplied 40 | case GetHistory => sender() ! history 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/calculator/CalculatorMain.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence.calculator 2 | 3 | import akka.actor._ 4 | 5 | object CalculatorMain extends App { 6 | val system = ActorSystem("calc") 7 | val calc = system.actorOf(Calculator.props, Calculator.name) 8 | 9 | calc ! Calculator.Add(1) 10 | calc ! Calculator.Multiply(3) 11 | calc ! Calculator.Divide(4) 12 | calc ! Calculator.PrintResult 13 | } 14 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/rest/ShopperMarshalling.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence.rest 2 | 3 | import scala.util.Try 4 | 5 | import spray.json._ 6 | 7 | import aia.persistence._ 8 | 9 | case class ItemNumber(number: Int) 10 | 11 | trait ShopperMarshalling extends DefaultJsonProtocol { 12 | implicit val basketItemFormat: RootJsonFormat[Item] = 13 | jsonFormat3(Item) 14 | implicit val basketFormat: RootJsonFormat[Items] = 15 | jsonFormat( 16 | (list: List[Item]) => Items.aggregate(list), "items" 17 | ) 18 | implicit val itemNumberFormat: RootJsonFormat[ItemNumber] = 19 | jsonFormat1(ItemNumber) 20 | } 21 | 22 | object ShopperMarshalling extends ShopperMarshalling 23 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/rest/ShoppersServiceSupport.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence.rest 2 | 3 | import com.typesafe.config.Config 4 | 5 | import scala.concurrent.duration._ 6 | import scala.concurrent.Future 7 | 8 | import akka.actor._ 9 | import akka.event.Logging 10 | import akka.io.IO 11 | import akka.pattern.ask 12 | import akka.util.Timeout 13 | 14 | import akka.http.scaladsl.Http 15 | import akka.http.scaladsl.Http.ServerBinding 16 | import akka.http.scaladsl.server.Directives._ 17 | import akka.stream.ActorMaterializer 18 | 19 | import aia.persistence._ 20 | 21 | trait ShoppersServiceSupport extends RequestTimeout { 22 | def startService(shoppers: ActorRef)(implicit system: ActorSystem) = { 23 | val config = system.settings.config 24 | val settings = Settings(system) 25 | val host = settings.http.host 26 | val port = settings.http.port 27 | 28 | implicit val ec = system.dispatcher //bindAndHandle requires an implicit ExecutionContext 29 | 30 | val api = new ShoppersService(shoppers, system, requestTimeout(config)).routes // the RestApi provides a Route 31 | 32 | implicit val materializer = ActorMaterializer() 33 | val bindingFuture: Future[ServerBinding] = 34 | Http().bindAndHandle(api, host, port) 35 | 36 | val log = Logging(system.eventStream, "shoppers") 37 | bindingFuture.map { serverBinding => 38 | log.info(s"Shoppers API bound to ${serverBinding.localAddress} ") 39 | }.onFailure { 40 | case ex: Exception => 41 | log.error(ex, "Failed to bind to {}:{}!", host, port) 42 | system.terminate() 43 | } 44 | } 45 | } 46 | 47 | trait RequestTimeout { 48 | import scala.concurrent.duration._ 49 | def requestTimeout(config: Config): Timeout = { 50 | val t = config.getString("akka.http.server.request-timeout") 51 | val d = Duration(t) 52 | FiniteDuration(d.length, d.unit) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/sharded/ShardedMain.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence.sharded 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import akka.actor._ 6 | import akka.io.IO 7 | import akka.pattern.ask 8 | import akka.util.Timeout 9 | 10 | import aia.persistence.rest.ShoppersServiceSupport 11 | 12 | object ShardedMain extends App with ShoppersServiceSupport { 13 | implicit val system = ActorSystem("shoppers") 14 | 15 | val shoppers = system.actorOf(ShardedShoppers.props, 16 | ShardedShoppers.name) 17 | 18 | startService(shoppers) 19 | } 20 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/sharded/ShardedShopper.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence.sharded 2 | 3 | import aia.persistence._ 4 | import akka.actor._ 5 | import akka.cluster.sharding.ShardRegion 6 | import akka.cluster.sharding.ShardRegion.Passivate 7 | 8 | object ShardedShopper { 9 | def props = Props(new ShardedShopper) 10 | def name(shopperId: Long) = shopperId.toString 11 | 12 | 13 | case object StopShopping 14 | 15 | val shardName: String = "shoppers" 16 | 17 | val extractEntityId: ShardRegion.ExtractEntityId = { 18 | case cmd: Shopper.Command => (cmd.shopperId.toString, cmd) 19 | } 20 | 21 | val extractShardId: ShardRegion.ExtractShardId = { 22 | case cmd: Shopper.Command => (cmd.shopperId % 12).toString 23 | } 24 | } 25 | 26 | class ShardedShopper extends Shopper { 27 | import ShardedShopper._ 28 | 29 | context.setReceiveTimeout(Settings(context.system).passivateTimeout) 30 | 31 | override def unhandled(msg: Any) = msg match { 32 | case ReceiveTimeout => 33 | context.parent ! Passivate(stopMessage = ShardedShopper.StopShopping) 34 | case StopShopping => context.stop(self) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter-persistence/src/main/scala/aia/persistence/sharded/ShardedShoppers.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence.sharded 2 | 3 | import aia.persistence._ 4 | import akka.actor._ 5 | import akka.cluster.sharding.{ClusterSharding, ClusterShardingSettings} 6 | 7 | object ShardedShoppers { 8 | def props= Props(new ShardedShoppers) 9 | def name = "sharded-shoppers" 10 | } 11 | 12 | class ShardedShoppers extends Actor { 13 | 14 | ClusterSharding(context.system).start( 15 | ShardedShopper.shardName, 16 | ShardedShopper.props, 17 | ClusterShardingSettings(context.system), 18 | ShardedShopper.extractEntityId, 19 | ShardedShopper.extractShardId 20 | ) 21 | 22 | def shardedShopper = { 23 | ClusterSharding(context.system).shardRegion(ShardedShopper.shardName) 24 | } 25 | 26 | def receive = { 27 | case cmd: Shopper.Command => 28 | shardedShopper forward cmd 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter-persistence/src/test/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka.persistence.journal.leveldb.native = off 2 | 3 | akka { 4 | loggers = ["akka.testkit.TestEventListener"] 5 | 6 | actor { 7 | provider = "akka.actor.LocalActorRefProvider" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter-persistence/src/test/scala/aia/persistence/BasketQuerySpec.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence 2 | 3 | import scala.concurrent.duration._ 4 | import scala.concurrent.Future 5 | import scala.concurrent.Await 6 | import akka.NotUsed 7 | import akka.actor._ 8 | import akka.testkit._ 9 | import org.scalatest._ 10 | 11 | import akka.stream._ 12 | import akka.stream.scaladsl._ 13 | import akka.persistence.query._ 14 | import akka.persistence.query.journal.leveldb.scaladsl._ 15 | 16 | class BasketQuerySpec extends PersistenceSpec(ActorSystem("test")) 17 | { 18 | 19 | val shopperId = 3L 20 | val macbookPro = Item("Apple Macbook Pro", 1, BigDecimal(2499.99)) 21 | val macPro = Item("Apple Mac Pro", 1, BigDecimal(10499.99)) 22 | val displays = Item("4K Display", 3, BigDecimal(2499.99)) 23 | val appleMouse = Item("Apple Mouse", 1, BigDecimal(99.99)) 24 | val appleKeyboard = Item("Apple Keyboard", 1, BigDecimal(79.99)) 25 | val dWave = Item("D-Wave One", 1, BigDecimal(14999999.99)) 26 | 27 | "Querying the journal for a basket" should { 28 | "return all basket events currently stored" in { 29 | import system.dispatcher 30 | val basket = system.actorOf(Basket.props, Basket.name(shopperId)) 31 | basket ! Basket.Add(macbookPro, shopperId) 32 | basket ! Basket.Add(displays, shopperId) 33 | basket ! Basket.GetItems(shopperId) 34 | expectMsg(Items(macbookPro, displays)) 35 | killActors(basket) 36 | 37 | implicit val mat = ActorMaterializer()(system) 38 | val queries = 39 | PersistenceQuery(system).readJournalFor[LeveldbReadJournal]( 40 | LeveldbReadJournal.Identifier 41 | ) 42 | 43 | val src: Source[EventEnvelope, NotUsed] = 44 | queries.currentEventsByPersistenceId( 45 | Basket.name(shopperId), 0L, Long.MaxValue) 46 | 47 | val events: Source[Basket.Event, NotUsed] = 48 | src.map(_.event.asInstanceOf[Basket.Event]) 49 | 50 | val res: Future[Seq[Basket.Event]] = events.runWith(Sink.seq) 51 | 52 | Await.result(res, 10 seconds) should equal( 53 | Vector( 54 | Basket.Added(macbookPro), 55 | Basket.Added(displays) 56 | ) 57 | ) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /chapter-persistence/src/test/scala/aia/persistence/BasketSpec.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import akka.actor._ 6 | import akka.testkit._ 7 | import org.scalatest._ 8 | 9 | class BasketSpec extends PersistenceSpec(ActorSystem("test")) 10 | with PersistenceCleanup { 11 | 12 | val shopperId = 2L 13 | val macbookPro = Item("Apple Macbook Pro", 1, BigDecimal(2499.99)) 14 | val macPro = Item("Apple Mac Pro", 1, BigDecimal(10499.99)) 15 | val displays = Item("4K Display", 3, BigDecimal(2499.99)) 16 | val appleMouse = Item("Apple Mouse", 1, BigDecimal(99.99)) 17 | val appleKeyboard = Item("Apple Keyboard", 1, BigDecimal(79.99)) 18 | val dWave = Item("D-Wave One", 1, BigDecimal(14999999.99)) 19 | 20 | // 買い物かご 21 | "The basket" should { 22 | // 回復処理ではClearイベントより前のイベントはスキップされる 23 | "skip basket events that occured before Cleared during recovery" in { 24 | val basket = system.actorOf(Basket.props, Basket.name(shopperId)) 25 | basket ! Basket.Add(macbookPro, shopperId) 26 | basket ! Basket.Add(displays, shopperId) 27 | basket ! Basket.GetItems(shopperId) 28 | expectMsg(Items(macbookPro, displays)) 29 | 30 | basket ! Basket.Clear(shopperId) 31 | 32 | basket ! Basket.Add(macPro, shopperId) 33 | basket ! Basket.RemoveItem(macPro.productId, shopperId) 34 | expectMsg(Some(Basket.ItemRemoved(macPro.productId))) 35 | 36 | basket ! Basket.Clear(shopperId) 37 | basket ! Basket.Add(dWave, shopperId) 38 | basket ! Basket.Add(displays, shopperId) 39 | 40 | basket ! Basket.GetItems(shopperId) 41 | expectMsg(Items(dWave, displays)) 42 | 43 | killActors(basket) 44 | 45 | val basketResurrected = system.actorOf(Basket.props, 46 | Basket.name(shopperId)) 47 | basketResurrected ! Basket.GetItems(shopperId) 48 | expectMsg(Items(dWave, displays)) 49 | 50 | basketResurrected ! Basket.CountRecoveredEvents(shopperId) 51 | expectMsg(Basket.RecoveredEventsCount(2)) 52 | 53 | killActors(basketResurrected) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /chapter-persistence/src/test/scala/aia/persistence/LocalShoppersSpec.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence 2 | 3 | import scala.concurrent.duration._ 4 | import akka.actor._ 5 | import akka.testkit._ 6 | import org.scalatest._ 7 | 8 | class LocalShoppersSpec extends PersistenceSpec(ActorSystem("test")) 9 | with PersistenceCleanup { 10 | 11 | val macbookPro = 12 | Item("Apple Macbook Pro", 1, BigDecimal(2499.99)) 13 | val macPro = Item("Apple Mac Pro", 1, BigDecimal(10499.99)) 14 | val displays = Item("4K Display", 3, BigDecimal(2499.99)) 15 | val appleMouse = Item("Apple Mouse", 1, BigDecimal(99.99)) 16 | val appleKeyboard = Item("Apple Keyboard", 1, BigDecimal(79.99)) 17 | 18 | "The local shoppers" should { 19 | "forward to the specific shopper" in { 20 | val probe = TestProbe() 21 | 22 | system.eventStream.subscribe(probe.ref, classOf[Wallet.Paid]) 23 | 24 | val shoppers = system.actorOf(LocalShoppers.props, LocalShoppers.name) 25 | val shopperId1 = 8 26 | val shopperId2 = 9 27 | 28 | shoppers ! Basket.Add(appleMouse, shopperId1) 29 | shoppers ! Basket.Add(appleKeyboard, shopperId1) 30 | shoppers ! Basket.GetItems(shopperId1) 31 | expectMsg(Items(appleMouse, appleKeyboard)) 32 | 33 | shoppers ! Basket.Add(displays, shopperId2) 34 | shoppers ! Basket.GetItems(shopperId2) 35 | expectMsg(Items(displays)) 36 | 37 | shoppers ! Shopper.PayBasket(shopperId1) 38 | probe.expectMsg(Wallet.Paid(List(appleMouse, appleKeyboard), shopperId1)) 39 | 40 | shoppers ! Shopper.PayBasket(shopperId2) 41 | probe.expectMsg(Wallet.Paid(List(displays), shopperId2)) 42 | 43 | killActors(shoppers) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /chapter-persistence/src/test/scala/aia/persistence/PersistenceSpec.scala: -------------------------------------------------------------------------------- 1 | package akka.testkit 2 | 3 | 4 | import java.io.File 5 | import com.typesafe.config._ 6 | 7 | import scala.util._ 8 | 9 | import akka.actor._ 10 | import akka.persistence._ 11 | import org.scalatest._ 12 | 13 | import org.apache.commons.io.FileUtils 14 | 15 | abstract class PersistenceSpec(system: ActorSystem) extends TestKit(system) 16 | with ImplicitSender 17 | with WordSpecLike 18 | with Matchers 19 | with BeforeAndAfterAll 20 | with PersistenceCleanup { 21 | 22 | def this(name: String, config: Config) = this(ActorSystem(name, config)) 23 | override protected def beforeAll() = deleteStorageLocations() 24 | 25 | override protected def afterAll() = { 26 | deleteStorageLocations() 27 | TestKit.shutdownActorSystem(system) 28 | } 29 | 30 | def killActors(actors: ActorRef*) = { 31 | actors.foreach { actor => 32 | watch(actor) 33 | system.stop(actor) 34 | expectTerminated(actor) 35 | Thread.sleep(1000) // the actor name is not unique intermittently on travis when creating it again after killActors, this is ducktape. 36 | } 37 | } 38 | } 39 | 40 | trait PersistenceCleanup { 41 | def system: ActorSystem 42 | 43 | val storageLocations = List( 44 | "akka.persistence.journal.leveldb.dir", 45 | "akka.persistence.journal.leveldb-shared.store.dir", 46 | "akka.persistence.snapshot-store.local.dir").map { s => 47 | new File(system.settings.config.getString(s)) 48 | } 49 | 50 | def deleteStorageLocations(): Unit = { 51 | storageLocations.foreach(dir => Try(FileUtils.deleteDirectory(dir))) 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /chapter-persistence/src/test/scala/aia/persistence/calculator/CalculatorSpec.scala: -------------------------------------------------------------------------------- 1 | package aia.persistence.calculator 2 | 3 | import akka.actor._ 4 | import akka.testkit._ 5 | import org.scalatest._ 6 | 7 | class CalculatorSpec extends PersistenceSpec(ActorSystem("test")) 8 | with PersistenceCleanup { 9 | 10 | // 電卓 11 | "The Calculator" should { 12 | // クラッシュ後、最後の正常状態に戻る 13 | "recover last known result after crash" in { 14 | val calc = system.actorOf(Calculator.props, Calculator.name) 15 | calc ! Calculator.Add(1d) 16 | calc ! Calculator.GetResult 17 | expectMsg(1d) 18 | 19 | calc ! Calculator.Subtract(0.5d) 20 | calc ! Calculator.GetResult 21 | expectMsg(0.5d) 22 | 23 | killActors(calc) 24 | 25 | val calcResurrected = system.actorOf(Calculator.props, Calculator.name) 26 | calcResurrected ! Calculator.GetResult 27 | expectMsg(0.5d) 28 | 29 | calcResurrected ! Calculator.Add(1d) 30 | calcResurrected ! Calculator.GetResult 31 | expectMsg(1.5d) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /chapter-remoting/Procfile: -------------------------------------------------------------------------------- 1 | web: target/start com.goticks.SingleNodeMain 2 | -------------------------------------------------------------------------------- /chapter-remoting/build.sbt: -------------------------------------------------------------------------------- 1 | name := "goticks" 2 | 3 | version := "1.0" 4 | 5 | organization := "com.goticks" 6 | 7 | libraryDependencies ++= { 8 | val akkaVersion = "2.5.4" 9 | val akkaHttpVersion ="10.0.10" 10 | Seq( 11 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-stream" % akkaVersion, 13 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 14 | 15 | "com.typesafe.akka" %% "akka-remote" % akkaVersion, 16 | "com.typesafe.akka" %% "akka-multi-node-testkit" % akkaVersion % "test", 17 | 18 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 19 | "org.scalatest" %% "scalatest" % "3.0.0" % "test", 20 | "com.typesafe.akka" %% "akka-http-core" % akkaHttpVersion, 21 | "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, 22 | "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion, 23 | "ch.qos.logback" % "logback-classic" % "1.1.6" 24 | ) 25 | } 26 | 27 | // Assembly settings 28 | mainClass in Global := Some("com.goticks.SingleNodeMain") 29 | 30 | assemblyJarName in assembly := "goticks-server.jar" 31 | -------------------------------------------------------------------------------- /chapter-remoting/project/GoticksBuild.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import com.typesafe.sbt.SbtMultiJvm 4 | import com.typesafe.sbt.SbtMultiJvm.MultiJvmKeys.{ MultiJvm } 5 | 6 | object GoTicksBuild extends Build { 7 | 8 | lazy val buildSettings = Defaults.defaultSettings ++ multiJvmSettings ++ Seq( 9 | crossPaths := false 10 | ) 11 | 12 | lazy val goticks = Project( 13 | id = "goticks", 14 | base = file("."), 15 | settings = buildSettings ++ Project.defaultSettings 16 | ) configs(MultiJvm) 17 | 18 | lazy val multiJvmSettings = SbtMultiJvm.multiJvmSettings ++ Seq( 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, multiJvmResults) => 27 | val overall = 28 | if (testResults.overall.id < multiJvmResults.overall.id) multiJvmResults.overall 29 | else testResults.overall 30 | Tests.Output(overall, 31 | testResults.events ++ multiJvmResults.events, 32 | testResults.summaries ++ multiJvmResults.summaries) 33 | } 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /chapter-remoting/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.15 2 | -------------------------------------------------------------------------------- /chapter-remoting/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Classpaths.typesafeResolver 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0") 4 | 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0") 6 | 7 | addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.11") 8 | -------------------------------------------------------------------------------- /chapter-remoting/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/resources/backend.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | stdout-loglevel = WARNING 4 | loggers = ["akka.event.slf4j.Slf4jLogger"] 5 | 6 | actor { 7 | provider = "akka.remote.RemoteActorRefProvider" 8 | } 9 | 10 | remote { 11 | enabled-transports = ["akka.remote.netty.tcp"] 12 | netty.tcp { 13 | hostname = "0.0.0.0" 14 | port = 2551 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /chapter-remoting/src/main/resources/frontend-remote-deploy.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | stdout-loglevel = DEBUG 4 | loggers = ["akka.event.slf4j.Slf4jLogger"] 5 | 6 | actor { 7 | provider = "akka.remote.RemoteActorRefProvider" 8 | 9 | deployment { 10 | /boxOffice { 11 | remote = "akka.tcp://backend@0.0.0.0:2551" 12 | } 13 | 14 | /forwarder/boxOffice { 15 | remote = "akka.tcp://backend@0.0.0.0:2551" 16 | } 17 | 18 | } 19 | } 20 | 21 | remote { 22 | enabled-transports = ["akka.remote.netty.tcp"] 23 | netty.tcp { 24 | hostname = "0.0.0.0" 25 | port = 2552 26 | } 27 | } 28 | 29 | http { 30 | server { 31 | server-header = "GoTicks.com REST API" 32 | } 33 | } 34 | } 35 | 36 | http { 37 | host = "0.0.0.0" 38 | host = ${?HOST} 39 | port = 5000 40 | port = ${?PORT} 41 | } 42 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/resources/frontend.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | stdout-loglevel = DEBUG 4 | loggers = ["akka.event.slf4j.Slf4jLogger"] 5 | 6 | actor { 7 | provider = "akka.remote.RemoteActorRefProvider" 8 | } 9 | 10 | remote { 11 | enabled-transports = ["akka.remote.netty.tcp"] 12 | netty.tcp { 13 | hostname = "0.0.0.0" 14 | port = 2552 15 | } 16 | } 17 | 18 | http { 19 | server { 20 | server-header = "GoTicks.com REST API" 21 | } 22 | } 23 | } 24 | 25 | http { 26 | host = "0.0.0.0" 27 | host = ${?HOST} 28 | port = 5000 29 | port = ${?PORT} 30 | } 31 | 32 | backend { 33 | host = "0.0.0.0" 34 | port = 2551 35 | protocol = "akka.tcp" 36 | system = "backend" 37 | actor = "user/boxOffice" 38 | } 39 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.out 5 | 6 | 7 | %-6level[%logger{0}]: %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/resources/singlenode.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | stdout-loglevel = DEBUG 4 | loggers = ["akka.event.slf4j.Slf4jLogger"] 5 | 6 | http { 7 | server { 8 | server-header = "GoTicks.com REST API" 9 | } 10 | } 11 | } 12 | 13 | http { 14 | host = "0.0.0.0" 15 | host = ${?HOST} 16 | port = 5000 17 | port = ${?PORT} 18 | } 19 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/scala/com/goticks/BackendMain.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.actor.ActorSystem 4 | import com.typesafe.config.ConfigFactory 5 | 6 | object BackendMain extends App with RequestTimeout { 7 | val config = ConfigFactory.load("backend") 8 | val system = ActorSystem("backend", config) 9 | implicit val requestTimeout = configuredRequestTimeout(config) 10 | system.actorOf(BoxOffice.props, BoxOffice.name) 11 | } 12 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/scala/com/goticks/BackendRemoteDeployMain.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import akka.actor.ActorSystem 5 | 6 | object BackendRemoteDeployMain extends App { 7 | val config = ConfigFactory.load("backend") 8 | val system = ActorSystem("backend", config) 9 | } 10 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/scala/com/goticks/EventMarshalling.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import spray.json._ 4 | 5 | case class EventDescription(tickets: Int) { 6 | require(tickets > 0) 7 | } 8 | 9 | case class TicketRequest(tickets: Int) { 10 | require(tickets > 0) 11 | } 12 | 13 | case class Error(message: String) 14 | 15 | trait EventMarshalling extends DefaultJsonProtocol { 16 | import BoxOffice._ 17 | 18 | implicit val eventDescriptionFormat = jsonFormat1(EventDescription) 19 | implicit val eventFormat = jsonFormat2(Event) 20 | implicit val eventsFormat = jsonFormat1(Events) 21 | implicit val ticketRequestFormat = jsonFormat1(TicketRequest) 22 | implicit val ticketFormat = jsonFormat1(TicketSeller.Ticket) 23 | implicit val ticketsFormat = jsonFormat2(TicketSeller.Tickets) 24 | implicit val errorFormat = jsonFormat1(Error) 25 | } 26 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/scala/com/goticks/FrontendMain.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.actor.{ ActorRef, ActorSystem, Props } 4 | import akka.event.Logging 5 | import com.typesafe.config.ConfigFactory 6 | 7 | object FrontendMain extends App 8 | with Startup { 9 | val config = ConfigFactory.load("frontend") 10 | 11 | implicit val system = ActorSystem("frontend", config) 12 | 13 | val api = new RestApi() { 14 | val log = Logging(system.eventStream, "frontend") 15 | implicit val requestTimeout = configuredRequestTimeout(config) 16 | implicit def executionContext = system.dispatcher 17 | 18 | def createPath(): String = { 19 | val config = ConfigFactory.load("frontend").getConfig("backend") 20 | val host = config.getString("host") 21 | val port = config.getInt("port") 22 | val protocol = config.getString("protocol") 23 | val systemName = config.getString("system") 24 | val actorName = config.getString("actor") 25 | s"$protocol://$systemName@$host:$port/$actorName" 26 | } 27 | 28 | def createBoxOffice: ActorRef = { 29 | val path = createPath() 30 | system.actorOf(Props(new RemoteLookupProxy(path)), "lookupBoxOffice") 31 | } 32 | } 33 | 34 | startup(api.routes) 35 | } 36 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/scala/com/goticks/FrontendRemoteDeployMain.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.actor.{ ActorRef, ActorSystem } 4 | import akka.event.Logging 5 | import com.typesafe.config.ConfigFactory 6 | 7 | object FrontendRemoteDeployMain extends App 8 | with Startup { 9 | val config = ConfigFactory.load("frontend-remote-deploy") 10 | implicit val system = ActorSystem("frontend", config) 11 | 12 | val api = new RestApi() { 13 | val log = Logging(system.eventStream, "frontend-remote") 14 | implicit val requestTimeout = configuredRequestTimeout(config) 15 | implicit def executionContext = system.dispatcher 16 | def createBoxOffice: ActorRef = system.actorOf(BoxOffice.props, BoxOffice.name) 17 | } 18 | 19 | startup(api.routes) 20 | } 21 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/scala/com/goticks/FrontendRemoteDeployWatchMain.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.actor.{ ActorRef, ActorSystem } 4 | import akka.event.Logging 5 | 6 | import com.typesafe.config.ConfigFactory 7 | 8 | object FrontendRemoteDeployWatchMain extends App 9 | with Startup { 10 | val config = ConfigFactory.load("frontend-remote-deploy") 11 | implicit val system = ActorSystem("frontend", config) 12 | 13 | val api = new RestApi() { 14 | val log = Logging(system.eventStream, "frontend-remote-watch") 15 | implicit val requestTimeout = configuredRequestTimeout(config) 16 | implicit def executionContext = system.dispatcher 17 | def createBoxOffice: ActorRef = { 18 | system.actorOf( 19 | RemoteBoxOfficeForwarder.props, 20 | RemoteBoxOfficeForwarder.name 21 | ) 22 | } 23 | } 24 | 25 | startup(api.routes) 26 | } 27 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/scala/com/goticks/RemoteBoxOfficeForwarder.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.actor._ 4 | import akka.util.Timeout 5 | 6 | import scala.concurrent.duration._ 7 | 8 | object RemoteBoxOfficeForwarder { 9 | def props(implicit timeout: Timeout) = { 10 | Props(new RemoteBoxOfficeForwarder) 11 | } 12 | def name = "forwarder" 13 | } 14 | 15 | class RemoteBoxOfficeForwarder(implicit timeout: Timeout) 16 | extends Actor with ActorLogging { 17 | context.setReceiveTimeout(3 seconds) 18 | 19 | deployAndWatch() 20 | 21 | def deployAndWatch(): Unit = { 22 | val actor = context.actorOf(BoxOffice.props, BoxOffice.name) 23 | context.watch(actor) 24 | log.info("switching to maybe active state") 25 | context.become(maybeActive(actor)) 26 | context.setReceiveTimeout(Duration.Undefined) 27 | } 28 | 29 | def receive = deploying 30 | 31 | def deploying: Receive = { 32 | 33 | case ReceiveTimeout => 34 | deployAndWatch() 35 | 36 | case msg: Any => 37 | log.error(s"Ignoring message $msg, remote actor is not ready yet.") 38 | } 39 | 40 | def maybeActive(actor: ActorRef): Receive = { 41 | case Terminated(actorRef) => 42 | log.info("Actor $actorRef terminated.") 43 | log.info("switching to deploying state") 44 | context.become(deploying) 45 | context.setReceiveTimeout(3 seconds) 46 | deployAndWatch() 47 | 48 | case msg: Any => actor forward msg 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/scala/com/goticks/RemoteLookupProxy.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.actor._ 4 | import akka.actor.ActorIdentity 5 | import akka.actor.Identify 6 | 7 | import scala.concurrent.duration._ 8 | 9 | class RemoteLookupProxy(path: String) 10 | extends Actor with ActorLogging { 11 | 12 | context.setReceiveTimeout(3 seconds) 13 | sendIdentifyRequest() 14 | 15 | def sendIdentifyRequest(): Unit = { 16 | val selection = context.actorSelection(path) 17 | selection ! Identify(path) 18 | } 19 | 20 | def receive = identify 21 | 22 | def identify: Receive = { 23 | case ActorIdentity(`path`, Some(actor)) => 24 | context.setReceiveTimeout(Duration.Undefined) 25 | log.info("switching to active state") 26 | context.become(active(actor)) 27 | context.watch(actor) 28 | 29 | case ActorIdentity(`path`, None) => 30 | log.error(s"Remote actor with path $path is not available.") 31 | 32 | case ReceiveTimeout => 33 | sendIdentifyRequest() 34 | 35 | case msg: Any => 36 | log.error(s"Ignoring message $msg, remote actor is not ready yet.") 37 | } 38 | 39 | def active(actor: ActorRef): Receive = { 40 | case Terminated(actorRef) => 41 | log.info("Actor $actorRef terminated.") 42 | log.info("switching to identify state") 43 | context.become(identify) 44 | context.setReceiveTimeout(3 seconds) 45 | sendIdentifyRequest() 46 | 47 | case msg: Any => actor forward msg 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/scala/com/goticks/RequestTimeout.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.util.Timeout 4 | import com.typesafe.config.Config 5 | 6 | trait RequestTimeout { 7 | import scala.concurrent.duration._ 8 | def configuredRequestTimeout(config: Config): Timeout = { 9 | val t = config.getString("akka.http.server.request-timeout") 10 | val d = Duration(t) 11 | FiniteDuration(d.length, d.unit) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/scala/com/goticks/SingleNodeMain.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.actor.{ ActorSystem, ActorRef } 4 | import akka.event.Logging 5 | 6 | import com.typesafe.config.ConfigFactory 7 | 8 | object SingleNodeMain extends App 9 | with Startup { 10 | val config = ConfigFactory.load("singlenode") 11 | implicit val system = ActorSystem("singlenode", config) 12 | 13 | val api = new RestApi() { 14 | val log = Logging(system.eventStream, "go-ticks") 15 | implicit val requestTimeout = configuredRequestTimeout(config) 16 | implicit def executionContext = system.dispatcher 17 | def createBoxOffice: ActorRef = system.actorOf(BoxOffice.props, BoxOffice.name) 18 | } 19 | 20 | startup(api.routes) 21 | } 22 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/scala/com/goticks/Startup.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import scala.concurrent.Future 4 | 5 | import akka.actor.ActorSystem 6 | import akka.event.Logging 7 | 8 | import akka.http.scaladsl.Http 9 | import akka.http.scaladsl.Http.ServerBinding 10 | import akka.http.scaladsl.server.Route 11 | 12 | import akka.stream.ActorMaterializer 13 | 14 | trait Startup extends RequestTimeout { 15 | def startup(api: Route)(implicit system: ActorSystem) = { 16 | val host = system.settings.config.getString("http.host") // Gets the host and a port from the configuration 17 | val port = system.settings.config.getInt("http.port") 18 | startHttpServer(api, host, port) 19 | } 20 | 21 | def startHttpServer(api: Route, host: String, port: Int) 22 | (implicit system: ActorSystem) = { 23 | implicit val ec = system.dispatcher //bindAndHandle requires an implicit ExecutionContext 24 | implicit val materializer = ActorMaterializer() 25 | val bindingFuture: Future[ServerBinding] = 26 | Http().bindAndHandle(api, host, port) //Starts the HTTP server 27 | 28 | val log = Logging(system.eventStream, "go-ticks") 29 | bindingFuture.map { serverBinding => 30 | log.info(s"RestApi bound to ${serverBinding.localAddress} ") 31 | }.onFailure { 32 | case ex: Exception => 33 | log.error(ex, "Failed to bind to {}:{}!", host, port) 34 | system.terminate() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter-remoting/src/main/scala/com/goticks/TicketSeller.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.actor.{ Actor, Props, PoisonPill } 4 | object TicketSeller { 5 | def props(event: String) = Props(new TicketSeller(event)) 6 | 7 | case class Add(tickets: Vector[Ticket]) 8 | case class Buy(tickets: Int) 9 | case class Ticket(id: Int) 10 | case class Tickets(event: String, 11 | entries: Vector[Ticket] = Vector.empty[Ticket]) 12 | case object GetEvent 13 | case object Cancel 14 | 15 | } 16 | 17 | 18 | class TicketSeller(event: String) extends Actor { 19 | import TicketSeller._ 20 | 21 | var tickets = Vector.empty[Ticket] 22 | 23 | def receive = { 24 | case Add(newTickets) => tickets = tickets ++ newTickets 25 | case Buy(nrOfTickets) => 26 | val entries = tickets.take(nrOfTickets).toVector 27 | if(entries.size >= nrOfTickets) { 28 | sender() ! Tickets(event, entries) 29 | tickets = tickets.drop(nrOfTickets) 30 | } else sender() ! Tickets(event) 31 | case GetEvent => sender() ! Some(BoxOffice.Event(event, tickets.size)) 32 | case Cancel => 33 | sender() ! Some(BoxOffice.Event(event, tickets.size)) 34 | self ! PoisonPill 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /chapter-remoting/src/multi-jvm/scala/com/goticks/ClientServerConfig.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.remote.testkit.MultiNodeConfig 4 | 5 | object ClientServerConfig extends MultiNodeConfig { 6 | val frontend = role("frontend") 7 | val backend = role("backend") 8 | } 9 | -------------------------------------------------------------------------------- /chapter-remoting/src/multi-jvm/scala/com/goticks/ClientServerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import scala.concurrent.duration._ 4 | import akka.remote.testkit.MultiNodeSpec 5 | import akka.util.Timeout 6 | 7 | import akka.testkit.ImplicitSender 8 | import akka.actor._ 9 | import TicketSeller._ 10 | 11 | class ClientServerSpecMultiJvmFrontend extends ClientServerSpec 12 | class ClientServerSpecMultiJvmBackend extends ClientServerSpec 13 | 14 | class ClientServerSpec extends MultiNodeSpec(ClientServerConfig) 15 | with STMultiNodeSpec with ImplicitSender { 16 | 17 | import ClientServerConfig._ 18 | 19 | val backendNode = node(backend) 20 | 21 | def initialParticipants = roles.size 22 | 23 | "A Client Server configured app" must { 24 | 25 | "wait for all nodes to enter a barrier" in { 26 | enterBarrier("startup") 27 | } 28 | 29 | "be able to create an event and sell a ticket" in { 30 | runOn(backend) { 31 | system.actorOf(BoxOffice.props(Timeout(1 second)), "boxOffice") 32 | enterBarrier("deployed") 33 | } 34 | 35 | runOn(frontend) { 36 | enterBarrier("deployed") 37 | 38 | val path = node(backend) / "user" / "boxOffice" 39 | val actorSelection = system.actorSelection(path) 40 | 41 | actorSelection.tell(Identify(path), testActor) 42 | 43 | val actorRef = expectMsgPF() { 44 | case ActorIdentity(`path`, Some(ref)) => ref 45 | } 46 | 47 | import BoxOffice._ 48 | 49 | actorRef ! CreateEvent("RHCP", 20000) 50 | 51 | expectMsg(EventCreated(Event("RHCP", 20000))) 52 | 53 | actorRef ! GetTickets("RHCP", 1) 54 | 55 | expectMsg(Tickets("RHCP", Vector(Ticket(1)))) 56 | } 57 | 58 | 59 | enterBarrier("finished") 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /chapter-remoting/src/multi-jvm/scala/com/goticks/STMultiNodeSpec.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.remote.testkit.MultiNodeSpecCallbacks 4 | import org.scalatest.{BeforeAndAfterAll, WordSpecLike} 5 | import org.scalatest.MustMatchers 6 | 7 | trait STMultiNodeSpec extends MultiNodeSpecCallbacks 8 | with WordSpecLike with MustMatchers with BeforeAndAfterAll { 9 | 10 | override def beforeAll() = multiNodeSpecBeforeAll() 11 | 12 | override def afterAll() = multiNodeSpecAfterAll() 13 | } 14 | -------------------------------------------------------------------------------- /chapter-remoting/src/test/scala/com/goticks/BoxOfficeSpec.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.actor.{ ActorRef, Props, ActorSystem } 4 | 5 | import akka.testkit.{ TestKit, ImplicitSender, DefaultTimeout } 6 | 7 | import org.scalatest.{ WordSpecLike, MustMatchers } 8 | 9 | class BoxOfficeSpec extends TestKit(ActorSystem("testBoxOffice")) 10 | with WordSpecLike 11 | with MustMatchers 12 | with ImplicitSender 13 | with DefaultTimeout 14 | with StopSystemAfterAll { 15 | "The BoxOffice" must { 16 | 17 | "Create an event and get tickets from the correct Ticket Seller" in { 18 | import BoxOffice._ 19 | import TicketSeller._ 20 | 21 | val boxOffice = system.actorOf(BoxOffice.props) 22 | val eventName = "RHCP" 23 | boxOffice ! CreateEvent(eventName, 10) 24 | expectMsg(EventCreated(Event(eventName, 10))) 25 | 26 | boxOffice ! GetTickets(eventName, 1) 27 | expectMsg(Tickets(eventName, Vector(Ticket(1)))) 28 | 29 | boxOffice ! GetTickets("DavidBowie", 1) 30 | expectMsg(Tickets("DavidBowie")) 31 | } 32 | 33 | "Create a child actor when an event is created and sends it a Tickets message" in { 34 | import BoxOffice._ 35 | import TicketSeller._ 36 | 37 | val boxOffice = system.actorOf(Props( 38 | new BoxOffice { 39 | override def createTicketSeller(name: String): ActorRef = testActor 40 | } 41 | ) 42 | ) 43 | 44 | val tickets = 3 45 | val eventName = "RHCP" 46 | val expectedTickets = (1 to tickets).map(Ticket).toVector 47 | boxOffice ! CreateEvent(eventName, tickets) 48 | expectMsg(Add(expectedTickets)) 49 | expectMsg(EventCreated(Event(eventName, tickets))) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /chapter-remoting/src/test/scala/com/goticks/StopSystemAfterAll.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import org.scalatest.{ Suite, BeforeAndAfterAll } 4 | import akka.testkit.TestKit 5 | 6 | trait StopSystemAfterAll extends BeforeAndAfterAll { 7 | this: TestKit with Suite => 8 | override protected def afterAll(): Unit = { 9 | super.afterAll() 10 | system.terminate() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-routing/build.sbt: -------------------------------------------------------------------------------- 1 | name := "routing" 2 | 3 | version := "1.0" 4 | 5 | organization := "com.manning" 6 | 7 | libraryDependencies ++= { 8 | val akkaVersion = "2.5.4" 9 | Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-remote" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 13 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 14 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /chapter-routing/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-routing/src/main/scala/aia/routing/HashRouting.scala: -------------------------------------------------------------------------------- 1 | package aia.routing 2 | 3 | import akka.actor.{ActorRef, Actor} 4 | import akka.routing.ConsistentHashingRouter.ConsistentHashable 5 | 6 | 7 | trait GatherMessage { 8 | val id: String 9 | val values: Seq[String] 10 | } 11 | 12 | case class GatherMessageNormalImpl(id: String, values: Seq[String]) extends GatherMessage 13 | 14 | case class GatherMessageWithHash(id: String, values: Seq[String]) extends GatherMessage with ConsistentHashable { 15 | override def consistentHashKey: Any = id 16 | } 17 | 18 | class SimpleGather(nextStep: ActorRef) extends Actor { 19 | var messages = Map[String, GatherMessage]() 20 | def receive = { 21 | case msg: GatherMessage => { 22 | messages.get(msg.id) match { 23 | case Some(previous) => { 24 | //join 25 | nextStep ! GatherMessageNormalImpl(msg.id, previous.values ++ msg.values) 26 | println("Joined: "+ msg.id + " by "+ self.path.name) 27 | messages -= msg.id 28 | } 29 | case None => messages += msg.id -> msg 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chapter-routing/src/main/scala/aia/routing/ImageProcessing.scala: -------------------------------------------------------------------------------- 1 | package aia.routing 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.Date 5 | 6 | case class Photo(license: String, speed: Int) 7 | 8 | object ImageProcessing { 9 | val dateFormat = new SimpleDateFormat("ddMMyyyy HH:mm:ss.SSS") 10 | def getSpeed(image: String): Option[Int] = { 11 | val attributes = image.split('|') 12 | if (attributes.size == 3) 13 | Some(attributes(1).toInt) 14 | else 15 | None 16 | } 17 | def getTime(image: String): Option[Date] = { 18 | val attributes = image.split('|') 19 | if (attributes.size == 3) 20 | Some(dateFormat.parse(attributes(0))) 21 | else 22 | None 23 | } 24 | def getLicense(image: String): Option[String] = { 25 | val attributes = image.split('|') 26 | if (attributes.size == 3) 27 | Some(attributes(2)) 28 | else 29 | None 30 | } 31 | def createPhotoString(date: Date, speed: Int): String = { 32 | createPhotoString(date, speed, " ") 33 | } 34 | 35 | def createPhotoString(date: Date, 36 | speed: Int, 37 | license: String): String = { 38 | "%s|%s|%s".format(dateFormat.format(date), speed, license) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /chapter-routing/src/test/scala/aia/routing/MsgRoutingTest.scala: -------------------------------------------------------------------------------- 1 | package aia.routing 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import akka.actor._ 6 | 7 | import org.scalatest._ 8 | 9 | import akka.testkit.{ TestProbe, TestKit } 10 | 11 | class MsgRoutingTest 12 | extends TestKit(ActorSystem("MsgRoutingTest")) 13 | with WordSpecLike with BeforeAndAfterAll { 14 | 15 | override def afterAll() = { 16 | system.terminate() 17 | } 18 | 19 | "The Router" must { 20 | "routes depending on speed" in { 21 | 22 | val normalFlowProbe = TestProbe() 23 | val cleanupProbe = TestProbe() 24 | val router = system.actorOf(Props.empty.withRouter( 25 | new SpeedRouterPool(50, 26 | Props(new RedirectActor(normalFlowProbe.ref)), 27 | Props(new RedirectActor(cleanupProbe.ref))) 28 | )) 29 | 30 | val msg = new Photo(license = "123xyz", speed = 60) 31 | router ! msg 32 | 33 | cleanupProbe.expectNoMsg(1 second) 34 | normalFlowProbe.expectMsg(msg) 35 | 36 | val msg2 = new Photo(license = "123xyz", speed = 45) 37 | router ! msg2 38 | 39 | cleanupProbe.expectMsg(msg2) 40 | normalFlowProbe.expectNoMsg(1 second) 41 | 42 | 43 | } 44 | /* "routes direct test" in { 45 | val normalFlowProbe = TestProbe() 46 | val cleanupProbe = TestProbe() 47 | val router = system.actorOf(Props.empty.withRouter( 48 | new SpeedRouter(50, 49 | Props(new RedirectActor(normalFlowProbe.ref)), 50 | Props(new RedirectActor(cleanupProbe.ref))))) 51 | 52 | val route = ExtractRoute(router) 53 | 54 | val r = Await.result( 55 | router.ask(CurrentRoutees)(1 second).mapTo[RouterRoutees], 56 | 1 second) 57 | r.routees.size must be(2) 58 | val normal = r.routees.head 59 | val clean = r.routees.last 60 | 61 | val msg = new Photo(license = "123xyz", speed = 60) 62 | route(testActor -> msg) must be(Seq( 63 | Destination(testActor, normal))) 64 | val msg2 = new Photo(license = "123xyz", speed = 45) 65 | route(testActor -> msg2) must be(Seq( 66 | Destination(testActor, clean))) 67 | 68 | 69 | } 70 | */ 71 | } 72 | } -------------------------------------------------------------------------------- /chapter-routing/src/test/scala/aia/routing/RouteSlipTest.scala: -------------------------------------------------------------------------------- 1 | package aia.routing 2 | 3 | import akka.actor._ 4 | import org.scalatest._ 5 | import akka.testkit._ 6 | 7 | class RouteSlipTest 8 | extends TestKit(ActorSystem("RouteSlipTest")) 9 | with WordSpecLike with BeforeAndAfterAll { 10 | 11 | override def afterAll(): Unit = { 12 | system.terminate() 13 | } 14 | 15 | "The Router" must { 16 | "route messages correctly" in { 17 | 18 | val probe = TestProbe() 19 | val router = system.actorOf( 20 | Props(new SlipRouter(probe.ref)), "SlipRouter") 21 | 22 | val minimalOrder = new Order(Seq()) 23 | router ! minimalOrder 24 | val defaultCar = new Car( 25 | color = "black", 26 | hasNavigation = false, 27 | hasParkingSensors = false) 28 | probe.expectMsg(defaultCar) 29 | 30 | 31 | 32 | val fullOrder = new Order(Seq( 33 | CarOptions.CAR_COLOR_GRAY, 34 | CarOptions.NAVIGATION, 35 | CarOptions.PARKING_SENSORS)) 36 | router ! fullOrder 37 | val carWithAllOptions = new Car( 38 | color = "gray", 39 | hasNavigation = true, 40 | hasParkingSensors = true) 41 | probe.expectMsg(carWithAllOptions) 42 | 43 | 44 | val msg = new Order(Seq(CarOptions.PARKING_SENSORS)) 45 | router ! msg 46 | val expectedCar = new Car( 47 | color = "black", 48 | hasNavigation = false, 49 | hasParkingSensors = true) 50 | probe.expectMsg(expectedCar) 51 | 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /chapter-state/build.sbt: -------------------------------------------------------------------------------- 1 | name := "state" 2 | 3 | version := "1.0" 4 | 5 | organization := "com.manning" 6 | 7 | libraryDependencies ++= { 8 | val akkaVersion = "2.5.4" 9 | Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-agent" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 13 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 14 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /chapter-state/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-state/src/main/scala/aia/state/Agent.scala: -------------------------------------------------------------------------------- 1 | package aia.state 2 | 3 | import akka.agent.Agent 4 | import akka.actor.ActorSystem 5 | import concurrent.Await 6 | import concurrent.duration._ 7 | import akka.util.Timeout 8 | //import concurrent.ExecutionContext.Implicits.global 9 | 10 | 11 | case class BookStatistics(val nameBook: String, nrSold: Int) 12 | case class StateBookStatistics(val sequence: Long, 13 | books: Map[String, BookStatistics]) 14 | 15 | 16 | class BookStatisticsMgr(system: ActorSystem) { 17 | implicit val ex = system.dispatcher //todo: change chapter 2.2 =>2.3 18 | val stateAgent = Agent(new StateBookStatistics(0, Map())) //todo: change chapter 2.2 =>2.3 19 | 20 | def addBooksSold(book: String, nrSold: Int): Unit = { 21 | stateAgent send (oldState => { 22 | val bookStat = oldState.books.get(book) match { 23 | case Some(bookState) => 24 | bookState.copy(nrSold = bookState.nrSold + nrSold) 25 | case None => new BookStatistics(book, nrSold) 26 | } 27 | oldState.copy(oldState.sequence + 1, 28 | oldState.books + (book -> bookStat)) 29 | }) 30 | } 31 | 32 | def addBooksSoldAndReturnNewState(book: String, 33 | nrSold: Int): StateBookStatistics = { 34 | val future = stateAgent alter (oldState => { 35 | val bookStat = oldState.books.get(book) match { 36 | case Some(bookState) => 37 | bookState.copy(nrSold = bookState.nrSold + nrSold) 38 | case None => new BookStatistics(book, nrSold) 39 | } 40 | oldState.copy(oldState.sequence + 1, 41 | oldState.books + (book -> bookStat)) 42 | }) 43 | Await.result(future, 1 second) 44 | } 45 | 46 | def getStateBookStatistics(): StateBookStatistics = { 47 | stateAgent.get() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /chapter-stream-integration/build.sbt: -------------------------------------------------------------------------------- 1 | name := "integration" 2 | 3 | version := "1.0" 4 | 5 | organization := "manning" 6 | 7 | libraryDependencies ++= { 8 | val akkaVersion = "2.5.4" 9 | val alpakkaVersion = "0.14" 10 | val akkaHttpVersion = "10.0.10" 11 | Seq( 12 | "org.scala-lang.modules" %% "scala-xml" % "1.0.6", 13 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 14 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 15 | "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, 16 | "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion, 17 | "com.typesafe.akka" %% "akka-http-xml" % akkaHttpVersion, 18 | "com.typesafe.akka" %% "akka-stream" % akkaVersion, 19 | "com.lightbend.akka" %% "akka-stream-alpakka-file" % alpakkaVersion, 20 | "com.lightbend.akka" %% "akka-stream-alpakka-amqp" % alpakkaVersion, 21 | "ch.qos.logback" % "logback-classic" % "1.1.3", 22 | "commons-io" % "commons-io" % "2.0.1" % "test", 23 | "io.arivera.oss" % "embedded-rabbitmq" % "1.2.1" % "test", 24 | "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % "test", 25 | "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion % "test", 26 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 27 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 28 | ) 29 | } 30 | 31 | -------------------------------------------------------------------------------- /chapter-stream-integration/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-stream-integration/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | stdout-loglevel = INFO 4 | loggers = ["akka.event.slf4j.Slf4jLogger"] 5 | default-dispatcher { 6 | fork-join-executor { 7 | parallelism-min = 8 8 | } 9 | } 10 | http { 11 | server { 12 | server-header = "OrderService REST API" 13 | } 14 | } 15 | } 16 | 17 | http { 18 | host = "0.0.0.0" 19 | host = ${?HOST} 20 | port = 5000 21 | port = ${?PORT} 22 | } 23 | -------------------------------------------------------------------------------- /chapter-stream-integration/src/main/scala/aia/stream/integration/OrderServiceApp.scala: -------------------------------------------------------------------------------- 1 | package aia.stream.integration 2 | 3 | import akka.actor.{ActorSystem, Props} 4 | import akka.event.Logging 5 | import akka.http.scaladsl.Http 6 | import akka.http.scaladsl.Http.ServerBinding 7 | import akka.stream.ActorMaterializer 8 | import akka.util.Timeout 9 | import com.typesafe.config.{Config, ConfigFactory} 10 | 11 | import scala.concurrent.Future 12 | 13 | object OrderServiceApp extends App 14 | with RequestTimeout { 15 | val config = ConfigFactory.load() 16 | val host = config.getString("http.host") 17 | val port = config.getInt("http.port") 18 | 19 | implicit val system = ActorSystem() 20 | implicit val ec = system.dispatcher 21 | 22 | implicit val materializer = ActorMaterializer() 23 | 24 | val log = Logging(system.eventStream, "order-service") 25 | } 26 | 27 | 28 | trait RequestTimeout { 29 | import scala.concurrent.duration._ 30 | def requestTimeout(config: Config): Timeout = { 31 | val t = config.getString("akka.http.server.request-timeout") 32 | val d = Duration(t) 33 | FiniteDuration(d.length, d.unit) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chapter-stream-integration/src/main/scala/aia/stream/integration/ProcessOrders.scala: -------------------------------------------------------------------------------- 1 | package aia.stream.integration 2 | 3 | import akka.actor.Actor 4 | 5 | import scala.collection.mutable 6 | import aia.stream.integration.Orders._ 7 | 8 | object ProcessOrders { 9 | 10 | case class TrackingOrder(id: Long, status: String, order: Order) 11 | 12 | case class OrderId(id: Long) 13 | 14 | case class NoSuchOrder(id: Long) 15 | } 16 | 17 | class ProcessOrders extends Actor { 18 | import ProcessOrders._ 19 | 20 | val orderList = new mutable.HashMap[Long, TrackingOrder]() 21 | var lastOrderId = 0L 22 | 23 | def receive = { 24 | case order: Order => { 25 | lastOrderId += 1 26 | val newOrder = new TrackingOrder(lastOrderId, "received", order) 27 | orderList += lastOrderId -> newOrder 28 | sender() ! newOrder 29 | } 30 | case order: OrderId => { 31 | orderList.get(order.id) match { 32 | case Some(intOrder) => 33 | sender() ! intOrder.copy(status = "processing") 34 | case None => sender() ! NoSuchOrder(order.id) 35 | } 36 | } 37 | case "reset" => { 38 | lastOrderId = 0 39 | orderList.clear() 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /chapter-stream-integration/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.out 5 | 6 | %-6level[%logger{0}]: %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter-stream-integration/src/test/scala/aia/stream/integration/OrderServiceTest.scala: -------------------------------------------------------------------------------- 1 | package aia.stream.integration 2 | 3 | import akka.actor.Props 4 | import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._ 5 | import akka.http.scaladsl.model.StatusCodes 6 | import akka.http.scaladsl.testkit.ScalatestRouteTest 7 | import org.scalatest.{Matchers, WordSpec} 8 | 9 | import scala.concurrent.duration._ 10 | import scala.xml.NodeSeq 11 | 12 | class OrderServiceTest extends WordSpec 13 | with Matchers 14 | with OrderService 15 | with ScalatestRouteTest { 16 | 17 | val processOrders = 18 | system.actorOf(Props(new ProcessOrders), "orders") 19 | 20 | implicit val executionContext = system.dispatcher 21 | implicit val requestTimeout = akka.util.Timeout(1 second) 22 | 23 | "The order service" should { 24 | 25 | "return NotFound if the order cannot be found" in { 26 | Get("/orders/1") ~> routes ~> check { 27 | status shouldEqual StatusCodes.NotFound 28 | } 29 | } 30 | 31 | "return the tracking order for an order that was posted" in { 32 | val xmlOrder = 33 | 34 | customer1 35 | Akka in action 36 |   10 37 | 38 | 39 | Post("/orders", xmlOrder) ~> routes ~> check { 40 | status shouldEqual StatusCodes.OK 41 | val xml = responseAs[NodeSeq] 42 | val id = (xml \\ "id").text.toInt 43 | val orderStatus = (xml \\ "status").text 44 | id shouldEqual 1 45 | orderStatus shouldEqual "received" 46 | } 47 | Get("/orders/1") ~> routes ~> check { 48 | status shouldEqual StatusCodes.OK 49 | val xml = responseAs[NodeSeq] 50 | val id = (xml \\ "id").text.toInt 51 | val orderStatus = (xml \\ "status").text 52 | id shouldEqual 1 53 | orderStatus shouldEqual "processing" 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /chapter-stream/build.sbt: -------------------------------------------------------------------------------- 1 | enablePlugins(JavaServerAppPackaging) 2 | 3 | name := "stream" 4 | 5 | version := "1.0" 6 | 7 | organization := "com.manning" 8 | 9 | libraryDependencies ++= { 10 | val akkaVersion = "2.5.4" 11 | val akkaHttpVersion = "10.0.10" 12 | Seq( 13 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 14 | // 15 | "com.typesafe.akka" %% "akka-stream" % akkaVersion, 16 | // 17 | // 18 | "com.typesafe.akka" %% "akka-http-core" % akkaHttpVersion, 19 | "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, 20 | "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion, 21 | // 22 | // 23 | "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion % "test", 24 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 25 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 26 | // 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /chapter-stream/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.15 2 | -------------------------------------------------------------------------------- /chapter-stream/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Classpaths.typesafeReleases 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0") 4 | 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0") -------------------------------------------------------------------------------- /chapter-stream/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-stream/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | stdout-loglevel = INFO 4 | 5 | http { 6 | server { 7 | server-header = "Log stream processor REST API" 8 | } 9 | } 10 | } 11 | 12 | http { 13 | host = "0.0.0.0" 14 | host = ${?HOST} 15 | port = 5000 16 | port = ${?PORT} 17 | } 18 | 19 | log-stream-processor { 20 | notifications-dir = "notifications" 21 | metrics-dir = "metrics" 22 | logs-dir = "logs" 23 | max-line = 10240 24 | max-json-object = 102400 25 | } -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/ContentNegLogsApp.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | import java.nio.file.{ Files, FileSystems, Path } 4 | import scala.concurrent.Future 5 | import scala.concurrent.duration._ 6 | 7 | import akka.NotUsed 8 | import akka.actor.{ ActorSystem , Actor, Props } 9 | import akka.event.Logging 10 | 11 | import akka.stream.{ ActorMaterializer, ActorMaterializerSettings, Supervision } 12 | 13 | import akka.http.scaladsl.Http 14 | import akka.http.scaladsl.Http.ServerBinding 15 | import akka.http.scaladsl.server.Directives._ 16 | 17 | import com.typesafe.config.{ Config, ConfigFactory } 18 | 19 | object ContentNegLogsApp extends App { 20 | 21 | val config = ConfigFactory.load() 22 | val host = config.getString("http.host") 23 | val port = config.getInt("http.port") 24 | 25 | val logsDir = { 26 | val dir = config.getString("log-stream-processor.logs-dir") 27 | Files.createDirectories(FileSystems.getDefault.getPath(dir)) 28 | } 29 | val maxLine = config.getInt("log-stream-processor.max-line") 30 | val maxJsObject = config.getInt("log-stream-processor.max-json-object") 31 | 32 | implicit val system = ActorSystem() 33 | implicit val ec = system.dispatcher 34 | 35 | val decider : Supervision.Decider = { 36 | case _: LogStreamProcessor.LogParseException => Supervision.Stop 37 | case _ => Supervision.Stop 38 | } 39 | 40 | implicit val materializer = ActorMaterializer( 41 | ActorMaterializerSettings(system) 42 | .withSupervisionStrategy(decider) 43 | ) 44 | 45 | val api = new ContentNegLogsApi(logsDir, maxLine, maxJsObject).routes 46 | 47 | val bindingFuture: Future[ServerBinding] = 48 | Http().bindAndHandle(api, host, port) 49 | 50 | val log = Logging(system.eventStream, "content-neg-logs") 51 | bindingFuture.map { serverBinding => 52 | log.info(s"Bound to ${serverBinding.localAddress} ") 53 | }.onFailure { 54 | case ex: Exception => 55 | log.error(ex, "Failed to bind to {}:{}!", host, port) 56 | system.terminate() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/Event.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | import java.io.File 4 | import java.time.ZonedDateTime 5 | import scala.concurrent.Future 6 | import akka.NotUsed 7 | import akka.util.ByteString 8 | import akka.stream.IOResult 9 | import akka.stream.scaladsl.{ Source, FileIO, Framing } 10 | 11 | import scala.concurrent.duration.FiniteDuration 12 | 13 | 14 | case class Event( 15 | host: String, 16 | service: String, 17 | state: State, 18 | time: ZonedDateTime, 19 | description: String, 20 | tag: Option[String] = None, 21 | metric: Option[Double] = None 22 | ) 23 | 24 | 25 | sealed trait State 26 | case object Critical extends State 27 | case object Error extends State 28 | case object Ok extends State 29 | case object Warning extends State 30 | 31 | object State { 32 | def norm(str: String): String = str.toLowerCase 33 | def norm(state: State): String = norm(state.toString) 34 | 35 | val ok = norm(Ok) 36 | val warning = norm(Warning) 37 | val error = norm(Error) 38 | val critical = norm(Critical) 39 | 40 | def unapply(str: String): Option[State] = { 41 | val normalized = norm(str) 42 | if(normalized == norm(Ok)) Some(Ok) 43 | else if(normalized == norm(Warning)) Some(Warning) 44 | else if(normalized == norm(Error)) Some(Error) 45 | else if(normalized == norm(Critical)) Some(Critical) 46 | else None 47 | } 48 | } 49 | 50 | case class LogReceipt(logId: String, written: Long) 51 | case class ParseError(logId: String, msg: String) -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/EventMarshalling.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | import java.time.ZonedDateTime 4 | import java.time.format.{ DateTimeFormatter, DateTimeParseException } 5 | 6 | import scala.util.Try 7 | import spray.json._ 8 | 9 | trait EventMarshalling extends DefaultJsonProtocol { 10 | implicit val dateTimeFormat = new JsonFormat[ZonedDateTime] { 11 | def write(dateTime: ZonedDateTime) = JsString(dateTime.format(DateTimeFormatter.ISO_INSTANT)) 12 | def read(value: JsValue) = value match { 13 | case JsString(str) => 14 | try { 15 | ZonedDateTime.parse(str) 16 | } catch { 17 | case e: DateTimeParseException => 18 | val msg = s"Could not deserialize $str to ZonedDateTime" 19 | deserializationError(msg) 20 | } 21 | case js => 22 | val msg = s"Could not deserialize $js to ZonedDateTime." 23 | deserializationError(msg) 24 | } 25 | } 26 | 27 | implicit val stateFormat = new JsonFormat[State] { 28 | def write(state: State) = JsString(State.norm(state)) 29 | def read(value: JsValue) = value match { 30 | case JsString("ok") => Ok 31 | case JsString("warning") => Warning 32 | case JsString("error") => Error 33 | case JsString("critical") => Critical 34 | case js => 35 | val msg = s"Could not deserialize $js to State." 36 | deserializationError(msg) 37 | } 38 | } 39 | 40 | implicit val eventFormat = jsonFormat7(Event) 41 | implicit val logIdFormat = jsonFormat2(LogReceipt) 42 | implicit val errorFormat = jsonFormat2(ParseError) 43 | } 44 | -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/EventUnmarshaller.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | import scala.concurrent.{ ExecutionContext, Future } 4 | import akka.NotUsed 5 | import akka.stream.scaladsl.Framing 6 | import akka.stream.scaladsl.JsonFraming 7 | import akka.stream.Materializer 8 | import akka.stream.scaladsl.Source 9 | 10 | import akka.http.scaladsl.model.HttpCharsets._ 11 | import akka.http.scaladsl.model.MediaTypes._ 12 | import akka.http.scaladsl.model.headers.Accept 13 | import akka.http.scaladsl.marshalling._ 14 | import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ 15 | import akka.http.scaladsl.model._ 16 | 17 | import akka.util.ByteString 18 | import spray.json._ 19 | 20 | import akka.http.scaladsl.unmarshalling.Unmarshaller 21 | import akka.http.scaladsl.unmarshalling.Unmarshaller._ 22 | 23 | object EventUnmarshaller extends EventMarshalling { 24 | val supported = Set[ContentTypeRange]( 25 | ContentTypes.`text/plain(UTF-8)`, 26 | ContentTypes.`application/json` 27 | ) 28 | 29 | def create(maxLine: Int, maxJsonObject: Int) = { 30 | new Unmarshaller[HttpEntity, Source[Event, _]] { 31 | def apply(entity: HttpEntity)(implicit ec: ExecutionContext, 32 | materializer: Materializer): Future[Source[Event, _]] = { 33 | 34 | val future = entity.contentType match { 35 | case ContentTypes.`text/plain(UTF-8)` => 36 | Future.successful(LogJson.textInFlow(maxLine)) 37 | case ContentTypes.`application/json` => 38 | Future.successful(LogJson.jsonInFlow(maxJsonObject)) 39 | case other => 40 | Future.failed( 41 | new UnsupportedContentTypeException(supported) 42 | ) 43 | } 44 | future.map(flow => entity.dataBytes.via(flow))(ec) 45 | } 46 | }.forContentTypes(supported.toList:_*) 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/FanLogsApp.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | import java.nio.file.{ Files, FileSystems, Path } 4 | import scala.concurrent.Future 5 | import scala.concurrent.duration._ 6 | 7 | import akka.NotUsed 8 | import akka.actor.{ ActorSystem , Actor, Props } 9 | import akka.event.Logging 10 | 11 | import akka.stream.{ ActorMaterializer, ActorMaterializerSettings, Supervision } 12 | 13 | import akka.http.scaladsl.Http 14 | import akka.http.scaladsl.Http.ServerBinding 15 | import akka.http.scaladsl.server.Directives._ 16 | 17 | import com.typesafe.config.{ Config, ConfigFactory } 18 | 19 | object FanLogsApp extends App { 20 | 21 | val config = ConfigFactory.load() 22 | val host = config.getString("http.host") 23 | val port = config.getInt("http.port") 24 | 25 | val logsDir = { 26 | val dir = config.getString("log-stream-processor.logs-dir") 27 | Files.createDirectories(FileSystems.getDefault.getPath(dir)) 28 | } 29 | val maxLine = config.getInt("log-stream-processor.max-line") 30 | val maxJsObject = config.getInt("log-stream-processor.max-json-object") 31 | 32 | implicit val system = ActorSystem() 33 | implicit val ec = system.dispatcher 34 | 35 | val decider : Supervision.Decider = { 36 | case _: LogStreamProcessor.LogParseException => Supervision.Resume 37 | case _ => Supervision.Stop 38 | } 39 | 40 | implicit val materializer = ActorMaterializer( 41 | ActorMaterializerSettings(system) 42 | .withSupervisionStrategy(decider) 43 | ) 44 | 45 | val api = new FanLogsApi(logsDir, maxLine, maxJsObject).routes 46 | 47 | val bindingFuture: Future[ServerBinding] = 48 | Http().bindAndHandle(api, host, port) 49 | 50 | val log = Logging(system.eventStream, "fan-logs") 51 | bindingFuture.map { serverBinding => 52 | log.info(s"Bound to ${serverBinding.localAddress} ") 53 | }.onFailure { 54 | case ex: Exception => 55 | log.error(ex, "Failed to bind to {}:{}!", host, port) 56 | system.terminate() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/FileArg.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | import java.nio.file.{ Path, Paths } 4 | 5 | object FileArg { 6 | def shellExpanded(path: String): Path = { 7 | if(path.startsWith("~/")) { 8 | Paths.get(System.getProperty("user.home"), path.drop(2)) 9 | } else { 10 | Paths.get(path) 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/GenerateLogFile.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | import java.nio.file.{ Path, Paths } 4 | import java.nio.file.StandardOpenOption 5 | import java.nio.file.StandardOpenOption._ 6 | import java.time.ZonedDateTime 7 | import java.time.format.DateTimeFormatter 8 | 9 | import scala.concurrent.Future 10 | 11 | import akka.actor.ActorSystem 12 | import akka.stream.{ ActorMaterializer, IOResult } 13 | import akka.stream.scaladsl._ 14 | import akka.util.ByteString 15 | 16 | object GenerateLogFile extends App { 17 | val filePath = args(0) 18 | val numberOfLines = args(1).toInt 19 | val rnd = new java.util.Random() 20 | val sink = FileIO.toPath(FileArg.shellExpanded(filePath), Set(CREATE, WRITE, APPEND)) 21 | def line(i: Int) = { 22 | val host = "my-host" 23 | val service = "my-service" 24 | val time = ZonedDateTime.now.format(DateTimeFormatter.ISO_INSTANT) 25 | val state = if( i % 10 == 0) "warning" 26 | else if(i % 101 == 0) "error" 27 | else if(i % 1002 == 0) "critical" 28 | else "ok" 29 | val description = "Some description of what has happened." 30 | val tag = "tag" 31 | val metric = rnd.nextDouble() * 100 32 | s"$host | $service | $state | $time | $description | $tag | $metric \n" 33 | } 34 | 35 | val graph = Source.fromIterator{() => 36 | Iterator.tabulate(numberOfLines)(line) 37 | }.map(l=> ByteString(l)).toMat(sink)(Keep.right) 38 | 39 | implicit val system = ActorSystem() 40 | implicit val ec = system.dispatcher 41 | implicit val materializer = ActorMaterializer() 42 | 43 | graph.run().foreach { result => 44 | println(s"Wrote ${result.count} bytes to '$filePath'.") 45 | system.terminate() 46 | } 47 | } -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/LogEntityMarshaller.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Framing 5 | import akka.stream.scaladsl.JsonFraming 6 | 7 | import akka.http.scaladsl.model.HttpCharsets._ 8 | import akka.http.scaladsl.model.MediaTypes._ 9 | import akka.http.scaladsl.model.headers.Accept 10 | import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ 11 | import akka.http.scaladsl.model._ 12 | 13 | import akka.stream.scaladsl.Source 14 | import akka.util.ByteString 15 | import spray.json._ 16 | 17 | 18 | import akka.http.scaladsl.marshalling.Marshaller 19 | import akka.http.scaladsl.marshalling.ToEntityMarshaller 20 | 21 | object LogEntityMarshaller extends EventMarshalling { 22 | 23 | type LEM = ToEntityMarshaller[Source[ByteString, _]] 24 | def create(maxJsonObject: Int): LEM = { 25 | val js = ContentTypes.`application/json` 26 | val txt = ContentTypes.`text/plain(UTF-8)` 27 | 28 | val jsMarshaller = Marshaller.withFixedContentType(js) { 29 | src:Source[ByteString, _] => 30 | HttpEntity(js, src) 31 | } 32 | 33 | val txtMarshaller = Marshaller.withFixedContentType(txt) { 34 | src:Source[ByteString, _] => 35 | HttpEntity(txt, toText(src, maxJsonObject)) 36 | } 37 | 38 | Marshaller.oneOf(jsMarshaller, txtMarshaller) 39 | } 40 | 41 | def toText(src: Source[ByteString, _], 42 | maxJsonObject: Int): Source[ByteString, _] = { 43 | src.via(LogJson.jsonToLogFlow(maxJsonObject)) 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/LogsApp.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | import java.nio.file.{ Files, FileSystems, Path } 4 | import scala.concurrent.Future 5 | import scala.concurrent.duration._ 6 | 7 | import akka.NotUsed 8 | import akka.actor.{ ActorSystem , Actor, Props } 9 | import akka.event.Logging 10 | 11 | import akka.stream.{ ActorMaterializer, ActorMaterializerSettings, Supervision } 12 | 13 | import akka.http.scaladsl.Http 14 | import akka.http.scaladsl.Http.ServerBinding 15 | import akka.http.scaladsl.server.Directives._ 16 | 17 | import com.typesafe.config.{ Config, ConfigFactory } 18 | 19 | object LogsApp extends App { 20 | 21 | val config = ConfigFactory.load() 22 | val host = config.getString("http.host") 23 | val port = config.getInt("http.port") 24 | 25 | val logsDir = { 26 | val dir = config.getString("log-stream-processor.logs-dir") 27 | Files.createDirectories(FileSystems.getDefault.getPath(dir)) 28 | } 29 | val maxLine = config.getInt("log-stream-processor.max-line") 30 | 31 | implicit val system = ActorSystem() 32 | implicit val ec = system.dispatcher 33 | 34 | val decider : Supervision.Decider = { 35 | case _: LogStreamProcessor.LogParseException => Supervision.Stop 36 | case _ => Supervision.Stop 37 | } 38 | 39 | implicit val materializer = ActorMaterializer( 40 | ActorMaterializerSettings(system) 41 | .withSupervisionStrategy(decider) 42 | ) 43 | 44 | val api = new LogsApi(logsDir, maxLine).routes 45 | 46 | val bindingFuture: Future[ServerBinding] = 47 | Http().bindAndHandle(api, host, port) 48 | 49 | val log = Logging(system.eventStream, "logs") 50 | bindingFuture.map { serverBinding => 51 | log.info(s"Bound to ${serverBinding.localAddress} ") 52 | }.onFailure { 53 | case ex: Exception => 54 | log.error(ex, "Failed to bind to {}:{}!", host, port) 55 | system.terminate() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/Metric.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | import java.time.ZonedDateTime 4 | import scala.concurrent.duration.FiniteDuration 5 | 6 | case class Metric( 7 | service: String, 8 | time: ZonedDateTime, 9 | metric: Double, 10 | tag: String, 11 | drift: Int = 0 12 | ) -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/MetricMarshalling.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | import scala.util.Try 4 | import spray.json._ 5 | 6 | trait MetricMarshalling extends EventMarshalling with DefaultJsonProtocol { 7 | implicit val metric = jsonFormat5(Metric) 8 | } 9 | -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/Notification.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | case class Summary(events: Vector[Event]) -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/NotificationMarshalling.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | import scala.concurrent.duration.FiniteDuration 4 | 5 | import scala.util.Try 6 | import spray.json._ 7 | 8 | trait NotificationMarshalling extends EventMarshalling with DefaultJsonProtocol { 9 | implicit val summary = jsonFormat1(Summary) 10 | } 11 | -------------------------------------------------------------------------------- /chapter-stream/src/main/scala/aia/stream/StreamingCopy.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | 3 | 4 | import java.nio.file.{ Path, Paths } 5 | import java.nio.file.StandardOpenOption 6 | import java.nio.file.StandardOpenOption._ 7 | import scala.concurrent.Future 8 | 9 | import akka.actor.ActorSystem 10 | import akka.stream.{ ActorMaterializer, IOResult } 11 | import akka.stream.scaladsl.{ FileIO, RunnableGraph, Source, Sink } 12 | import akka.util.ByteString 13 | 14 | 15 | import com.typesafe.config.{ Config, ConfigFactory } 16 | 17 | object StreamingCopy extends App { 18 | val config = ConfigFactory.load() 19 | val maxLine = config.getInt("log-stream-processor.max-line") 20 | 21 | if(args.length != 2) { 22 | System.err.println("Provide args: input-file output-file") 23 | System.exit(1) 24 | } 25 | 26 | val inputFile = FileArg.shellExpanded(args(0)) 27 | val outputFile = FileArg.shellExpanded(args(1)) 28 | 29 | 30 | val source: Source[ByteString, Future[IOResult]] = 31 | FileIO.fromPath(inputFile) 32 | 33 | val sink: Sink[ByteString, Future[IOResult]] = 34 | FileIO.toPath(outputFile, Set(CREATE, WRITE, APPEND)) 35 | 36 | val runnableGraph: RunnableGraph[Future[IOResult]] = 37 | source.to(sink) 38 | 39 | 40 | 41 | 42 | 43 | implicit val system = ActorSystem() 44 | implicit val ec = system.dispatcher 45 | implicit val materializer = ActorMaterializer() 46 | 47 | runnableGraph.run().foreach { result => 48 | println(s"${result.status}, ${result.count} bytes read.") 49 | system.terminate() 50 | } 51 | 52 | 53 | // These are just examples, they are not run as part of StreamingCopy 54 | 55 | import akka.Done 56 | import akka.stream.scaladsl.Keep 57 | 58 | val graphLeft: RunnableGraph[Future[IOResult]] = 59 | source.toMat(sink)(Keep.left) 60 | val graphRight: RunnableGraph[Future[IOResult]] = 61 | source.toMat(sink)(Keep.right) 62 | val graphBoth: RunnableGraph[(Future[IOResult], Future[IOResult])] = 63 | source.toMat(sink)(Keep.both) 64 | val graphCustom: RunnableGraph[Future[Done]] = 65 | source.toMat(sink) { (l, r) => 66 | Future.sequence(List(l,r)).map(_ => Done) 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /chapter-stream/src/test/scala/aia/stream/StopSystemAfterAll.scala: -------------------------------------------------------------------------------- 1 | package aia.stream 2 | import org.scalatest.{ Suite, BeforeAndAfterAll } 3 | import akka.testkit.TestKit 4 | 5 | trait StopSystemAfterAll extends BeforeAndAfterAll { 6 | this: TestKit with Suite => 7 | override protected def afterAll(): Unit = { 8 | super.afterAll() 9 | system.terminate() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /chapter-structure/build.sbt: -------------------------------------------------------------------------------- 1 | name := "structure" 2 | 3 | version := "1.0" 4 | 5 | organization := "com.manning" 6 | 7 | scalacOptions ++= Seq( 8 | "-deprecation", 9 | "-unchecked", 10 | "-Xlint", 11 | "-Ywarn-unused", 12 | "-Ywarn-dead-code", 13 | "-feature", 14 | "-language:_" 15 | ) 16 | 17 | libraryDependencies ++= { 18 | val akkaVersion = "2.5.4" 19 | Seq( 20 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 21 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 22 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 23 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /chapter-structure/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-structure/src/main/scala/aia/structure/Filters.scala: -------------------------------------------------------------------------------- 1 | package aia.structure 2 | 3 | import akka.actor.{ Actor, ActorRef } 4 | 5 | 6 | case class Photo(license: String, speed: Int) 7 | 8 | class SpeedFilter(minSpeed: Int, pipe: ActorRef) extends Actor { 9 | def receive = { 10 | case msg: Photo => 11 | if (msg.speed > minSpeed) 12 | pipe ! msg 13 | } 14 | } 15 | 16 | class LicenseFilter(pipe: ActorRef) extends Actor { 17 | def receive = { 18 | case msg: Photo => 19 | if (!msg.license.isEmpty) 20 | pipe ! msg 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chapter-structure/src/test/scala/aia/structure/PipeAndFilterTest.scala: -------------------------------------------------------------------------------- 1 | package aia.structure 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import akka.actor._ 6 | 7 | import org.scalatest._ 8 | import akka.testkit._ 9 | import scala.language.postfixOps 10 | 11 | class PipeAndFilterTest 12 | extends TestKit(ActorSystem("PipeAndFilterTest")) 13 | with WordSpecLike 14 | with BeforeAndAfterAll { 15 | 16 | val timeout = 2 seconds 17 | 18 | override def afterAll(): Unit = { 19 | system.terminate() 20 | } 21 | 22 | "The pipe and filter" must { 23 | "filter messages in configuration 1" in { 24 | 25 | val endProbe = TestProbe() 26 | val speedFilterRef = system.actorOf( 27 | Props(new SpeedFilter(50, endProbe.ref))) 28 | val licenseFilterRef = system.actorOf( 29 | Props(new LicenseFilter(speedFilterRef))) 30 | val msg = new Photo("123xyz", 60) 31 | licenseFilterRef ! msg 32 | endProbe.expectMsg(msg) 33 | 34 | licenseFilterRef ! new Photo("", 60) 35 | endProbe.expectNoMsg(timeout) 36 | 37 | licenseFilterRef ! new Photo("123xyz", 49) 38 | endProbe.expectNoMsg(timeout) 39 | } 40 | "filter messages in configuration 2" in { 41 | 42 | val endProbe = TestProbe() 43 | val licenseFilterRef = system.actorOf( 44 | Props(new LicenseFilter(endProbe.ref))) 45 | val speedFilterRef = system.actorOf( 46 | Props(new SpeedFilter(50, licenseFilterRef))) 47 | val msg = new Photo("123xyz", 60) 48 | speedFilterRef ! msg 49 | endProbe.expectMsg(msg) 50 | 51 | speedFilterRef ! new Photo("", 60) 52 | endProbe.expectNoMsg(timeout) 53 | 54 | speedFilterRef ! new Photo("123xyz", 49) 55 | endProbe.expectNoMsg(timeout) 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /chapter-structure/src/test/scala/aia/structure/RecipientListTest.scala: -------------------------------------------------------------------------------- 1 | package aia.structure 2 | 3 | import akka.actor._ 4 | import org.scalatest._ 5 | import akka.testkit._ 6 | 7 | class RecipientListTest 8 | extends TestKit(ActorSystem("RecipientListTest")) 9 | with WordSpecLike 10 | with BeforeAndAfterAll { 11 | 12 | override def afterAll(): Unit = { 13 | system.terminate() 14 | } 15 | 16 | "The RecipientList" must { 17 | "scatter the message" in { 18 | 19 | val endProbe1 = TestProbe() 20 | val endProbe2 = TestProbe() 21 | val endProbe3 = TestProbe() 22 | val list = Seq(endProbe1.ref, endProbe2.ref, endProbe3.ref) 23 | val actorRef = system.actorOf( 24 | Props(new RecipientList(list))) 25 | val msg = "message" 26 | actorRef ! msg 27 | endProbe1.expectMsg(msg) 28 | endProbe2.expectMsg(msg) 29 | endProbe3.expectMsg(msg) 30 | 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chapter-structure/src/test/scala/aia/structure/ScatterGatherTest.scala: -------------------------------------------------------------------------------- 1 | package aia.structure 2 | 3 | import java.util.Date 4 | import scala.concurrent.duration._ 5 | 6 | import akka.actor._ 7 | 8 | import org.scalatest._ 9 | import akka.testkit._ 10 | import scala.language.postfixOps 11 | 12 | class ScatterGatherTest 13 | extends TestKit(ActorSystem("ScatterGatherTest")) 14 | with WordSpecLike 15 | with BeforeAndAfterAll { 16 | 17 | val timeout = 2 seconds 18 | 19 | override def afterAll(): Unit = { 20 | system.terminate() 21 | } 22 | 23 | "The ScatterGather" must { 24 | "scatter the message and gather them again" in { 25 | 26 | val endProbe = TestProbe() 27 | val aggregateRef = system.actorOf( 28 | Props(new Aggregator(timeout, endProbe.ref))) 29 | val speedRef = system.actorOf( 30 | Props(new GetSpeed(aggregateRef))) 31 | val timeRef = system.actorOf( 32 | Props(new GetTime(aggregateRef))) 33 | val actorRef = system.actorOf( 34 | Props(new RecipientList(Seq(speedRef, timeRef)))) 35 | 36 | val photoDate = new Date() 37 | val photoSpeed = 60 38 | val msg = PhotoMessage("id1", 39 | ImageProcessing.createPhotoString(photoDate, photoSpeed)) 40 | 41 | actorRef ! msg 42 | 43 | val combinedMsg = PhotoMessage(msg.id, 44 | msg.photo, 45 | Some(photoDate), 46 | Some(photoSpeed)) 47 | 48 | endProbe.expectMsg(combinedMsg) 49 | 50 | 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /chapter-testdriven/build.sbt: -------------------------------------------------------------------------------- 1 | name := "testdriven" 2 | 3 | version := "1.0" 4 | 5 | organization := "com.manning" 6 | 7 | libraryDependencies ++= { 8 | val akkaVersion = "2.5.4" 9 | Seq( 10 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 11 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 12 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 13 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /chapter-testdriven/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-testdriven/src/main/scala/aia/testdriven/Greeter.scala: -------------------------------------------------------------------------------- 1 | package aia.testdriven 2 | 3 | 4 | import akka.actor.{ActorLogging, Actor} 5 | 6 | case class Greeting(message: String) 7 | 8 | class Greeter extends Actor with ActorLogging { 9 | def receive = { 10 | case Greeting(message) => log.info("Hello {}!", message) 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /chapter-testdriven/src/test/scala/aia/testdriven/EchoActorTest.scala: -------------------------------------------------------------------------------- 1 | package aia.testdriven 2 | 3 | import akka.testkit.{ TestKit, ImplicitSender } 4 | import akka.actor.{ Props, Actor, ActorSystem } 5 | import org.scalatest.WordSpecLike 6 | 7 | import akka.util.Timeout 8 | import scala.concurrent.Await 9 | import scala.util.{ Success, Failure } 10 | 11 | import scala.language.postfixOps 12 | 13 | 14 | class EchoActorTest extends TestKit(ActorSystem("testsystem")) 15 | with WordSpecLike 16 | with ImplicitSender 17 | with StopSystemAfterAll { 18 | 19 | 20 | "An EchoActor" must { 21 | "Reply with the same message it receives" in { 22 | 23 | import akka.pattern.ask 24 | import scala.concurrent.duration._ 25 | implicit val timeout = Timeout(3 seconds) 26 | implicit val ec = system.dispatcher 27 | val echo = system.actorOf(Props[EchoActor]) 28 | val future = echo.ask("some message") 29 | future.onComplete { 30 | case Failure(_) => // 失敗時の制御 31 | case Success(msg) => // 成功時の制御 32 | } 33 | 34 | Await.ready(future, timeout.duration) 35 | } 36 | 37 | "Reply with the same message it receives without ask" in { 38 | val echo = system.actorOf(Props[EchoActor]) 39 | echo ! "some message" 40 | expectMsg("some message") 41 | 42 | } 43 | 44 | } 45 | } 46 | 47 | 48 | class EchoActor extends Actor { 49 | def receive = { 50 | case msg => 51 | sender() ! msg 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /chapter-testdriven/src/test/scala/aia/testdriven/Greeter01Test.scala: -------------------------------------------------------------------------------- 1 | package aia.testdriven 2 | import akka.testkit.{ CallingThreadDispatcher, EventFilter, TestKit } 3 | import akka.actor.{ Props, ActorSystem } 4 | import com.typesafe.config.ConfigFactory 5 | import org.scalatest.WordSpecLike 6 | 7 | 8 | import Greeter01Test._ 9 | 10 | class Greeter01Test extends TestKit(testSystem) 11 | with WordSpecLike 12 | with StopSystemAfterAll { 13 | 14 | "The Greeter" must { 15 | "say Hello World! when a Greeting(\"World\") is sent to it" in { 16 | val dispatcherId = CallingThreadDispatcher.Id 17 | val props = Props[Greeter].withDispatcher(dispatcherId) 18 | val greeter = system.actorOf(props) 19 | EventFilter.info(message = "Hello World!", 20 | occurrences = 1).intercept { 21 | greeter ! Greeting("World") 22 | } 23 | } 24 | } 25 | } 26 | 27 | object Greeter01Test { 28 | val testSystem = { 29 | val config = ConfigFactory.parseString( 30 | """ 31 | akka.loggers = [akka.testkit.TestEventListener] 32 | """) 33 | ActorSystem("testsystem", config) 34 | } 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /chapter-testdriven/src/test/scala/aia/testdriven/Greeter02Test.scala: -------------------------------------------------------------------------------- 1 | package aia.testdriven 2 | 3 | import akka.testkit.{ TestKit } 4 | import org.scalatest.WordSpecLike 5 | import akka.actor._ 6 | 7 | 8 | 9 | class Greeter02Test extends TestKit(ActorSystem("testsystem")) 10 | with WordSpecLike 11 | with StopSystemAfterAll { 12 | 13 | "The Greeter" must { 14 | "say Hello World! when a Greeting(\"World\") is sent to it" in { 15 | val props = Greeter02.props(Some(testActor)) 16 | val greeter = system.actorOf(props, "greeter02-1") 17 | greeter ! Greeting("World") 18 | expectMsg("Hello World!") 19 | } 20 | "say something else and see what happens" in { 21 | val props = Greeter02.props(Some(testActor)) 22 | val greeter = system.actorOf(props, "greeter02-2") 23 | system.eventStream.subscribe(testActor, classOf[UnhandledMessage]) 24 | greeter ! "World" 25 | expectMsg(UnhandledMessage("World", system.deadLetters, greeter)) 26 | } 27 | } 28 | } 29 | 30 | 31 | object Greeter02 { 32 | def props(listener: Option[ActorRef] = None) = 33 | Props(new Greeter02(listener)) 34 | } 35 | class Greeter02(listener: Option[ActorRef]) 36 | extends Actor with ActorLogging { 37 | def receive = { 38 | case Greeting(who) => 39 | val message = "Hello " + who + "!" 40 | log.info(message) 41 | listener.foreach(_ ! message) 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /chapter-testdriven/src/test/scala/aia/testdriven/SendingActorTest.scala: -------------------------------------------------------------------------------- 1 | package aia.testdriven 2 | 3 | import scala.util.Random 4 | import akka.testkit.TestKit 5 | import akka.actor.{ Props, ActorRef, Actor, ActorSystem } 6 | import org.scalatest.{WordSpecLike, MustMatchers} 7 | 8 | class SendingActorTest extends TestKit(ActorSystem("testsystem")) 9 | with WordSpecLike 10 | with MustMatchers 11 | with StopSystemAfterAll { 12 | 13 | "A Sending Actor" must { 14 | "send a message to another actor when it has finished processing" in { 15 | import SendingActor._ 16 | val props = SendingActor.props(testActor) 17 | val sendingActor = system.actorOf(props, "sendingActor") 18 | 19 | val size = 1000 20 | val maxInclusive = 100000 21 | 22 | def randomEvents() = (0 until size).map{ _ => 23 | Event(Random.nextInt(maxInclusive)) 24 | }.toVector 25 | 26 | val unsorted = randomEvents() 27 | val sortEvents = SortEvents(unsorted) 28 | sendingActor ! sortEvents 29 | 30 | expectMsgPF() { 31 | case SortedEvents(events) => 32 | events.size must be(size) 33 | unsorted.sortBy(_.id) must be(events) 34 | } 35 | } 36 | } 37 | } 38 | 39 | object SendingActor { 40 | def props(receiver: ActorRef) = 41 | Props(new SendingActor(receiver)) 42 | case class Event(id: Long) 43 | case class SortEvents(unsorted: Vector[Event]) 44 | case class SortedEvents(sorted: Vector[Event]) 45 | } 46 | 47 | class SendingActor(receiver: ActorRef) extends Actor { 48 | import SendingActor._ 49 | def receive = { 50 | case SortEvents(unsorted) => 51 | receiver ! SortedEvents(unsorted.sortBy(_.id)) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /chapter-testdriven/src/test/scala/aia/testdriven/SilentActor01Test.scala: -------------------------------------------------------------------------------- 1 | package aia.testdriven 2 | 3 | import org.scalatest.{WordSpecLike, MustMatchers} 4 | import akka.testkit.TestKit 5 | import akka.actor._ 6 | 7 | //This test is ignored in the BookBuild, it's added to the defaultExcludedNames 8 | 9 | class SilentActor01Test extends TestKit(ActorSystem("testsystem")) 10 | with WordSpecLike 11 | with MustMatchers 12 | with StopSystemAfterAll { 13 | // travisのビルドをパスするためコメントアウトしますが、こちらが書籍内のテストです。 14 | // "A Silent Actor" must { 15 | // "change state when it receives a message, single threaded" in { 16 | // // テストを書くと最初は失敗する 17 | // fail("not implemented yet") 18 | // } 19 | // "change state when it receives a message, multi-threaded" in { 20 | // // テストを書くと最初は失敗する 21 | // fail("not implemented yet") 22 | // } 23 | // } 24 | "A Silent Actor" must { 25 | "change state when it receives a message, single threaded" ignore { 26 | // テストを書くと最初は失敗する 27 | fail("not implemented yet") 28 | } 29 | "change state when it receives a message, multi-threaded" ignore { 30 | // テストを書くと最初は失敗する 31 | fail("not implemented yet") 32 | } 33 | } 34 | 35 | } 36 | 37 | 38 | 39 | class SilentActor extends Actor { 40 | def receive = { 41 | case msg => 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /chapter-testdriven/src/test/scala/aia/testdriven/StopSystemAfterAll.scala: -------------------------------------------------------------------------------- 1 | package aia.testdriven 2 | 3 | import org.scalatest.{ Suite, BeforeAndAfterAll } 4 | import akka.testkit.TestKit 5 | 6 | trait StopSystemAfterAll extends BeforeAndAfterAll { 7 | 8 | this: TestKit with Suite => 9 | override protected def afterAll(): Unit = { 10 | super.afterAll() 11 | system.terminate() 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /chapter-up-and-running/Procfile: -------------------------------------------------------------------------------- 1 | web: target/universal/stage/bin/goticks 2 | -------------------------------------------------------------------------------- /chapter-up-and-running/README.md: -------------------------------------------------------------------------------- 1 | Heroku deployment 2 | ================= 3 | 4 | Heroku normally expects the project to reside in the root of the git repo. 5 | the source code for the up and running chapter is not in the root of the repo, so you need to use a different command to deploy to heroku: 6 | 7 | git subtree push --prefix chapter-up-and-running heroku master 8 | 9 | This command has to be executed from the root of the git repo, not from within the chapter directory. 10 | The git subtree command is not as featured as the normal push command, for instance, it does not provide a flag to force push, 11 | and it does not support the : syntax which you can use with git push: 12 | 13 | git push heroku my-localbranch:master 14 | 15 | Which is normally used to deploy from a branch to heroku (pushing a branch to heroku master). 16 | It is possible to nest commands though, so if you want to push from a branch you can do the following: 17 | 18 | git push heroku `git subtree split --prefix chapter-up-and-running my-local-branch`:master 19 | 20 | Where *my-local-branch* is your local branch. 21 | Forcing a push can be done by nesting commands as well: 22 | 23 | git push heroku `git subtree split --prefix chapter-up-and-running master`:master --force 24 | 25 | The above pushes the changes in local master to heroku master. 26 | 27 | 28 | -------------------------------------------------------------------------------- /chapter-up-and-running/build.sbt: -------------------------------------------------------------------------------- 1 | enablePlugins(JavaServerAppPackaging) 2 | 3 | name := "goticks" 4 | 5 | version := "1.0" 6 | 7 | organization := "com.goticks" 8 | 9 | libraryDependencies ++= { 10 | val akkaVersion = "2.5.4" 11 | val akkaHttpVersion = "10.0.10" 12 | Seq( 13 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 14 | "com.typesafe.akka" %% "akka-stream" % akkaVersion, 15 | "com.typesafe.akka" %% "akka-http-core" % akkaHttpVersion, 16 | "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, 17 | "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion, 18 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 19 | "ch.qos.logback" % "logback-classic" % "1.1.3", 20 | "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test", 21 | "org.scalatest" %% "scalatest" % "3.0.0" % "test" 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /chapter-up-and-running/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.15 2 | -------------------------------------------------------------------------------- /chapter-up-and-running/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Classpaths.typesafeReleases 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0") 4 | 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0") 6 | -------------------------------------------------------------------------------- /chapter-up-and-running/scala.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.3" 2 | 3 | scalacOptions ++= Seq( 4 | "-deprecation", 5 | "-unchecked", 6 | "-Xlint", 7 | "-Ywarn-unused", 8 | "-Ywarn-dead-code", 9 | "-feature", 10 | "-language:_" 11 | ) 12 | -------------------------------------------------------------------------------- /chapter-up-and-running/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = INFO 3 | stdout-loglevel = INFO 4 | loggers = ["akka.event.slf4j.Slf4jLogger"] 5 | logger-startup-timeout = 30s 6 | default-dispatcher { 7 | fork-join-executor { 8 | parallelism-min = 8 9 | } 10 | } 11 | test { 12 | timefactor = 1 13 | } 14 | http { 15 | server { 16 | server-header = "GoTicks.com REST API" 17 | } 18 | } 19 | } 20 | 21 | http { 22 | host = "0.0.0.0" 23 | host = ${?HOST} 24 | port = 5000 25 | port = ${?PORT} 26 | } 27 | -------------------------------------------------------------------------------- /chapter-up-and-running/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.out 5 | 6 | 7 | %-6level[%logger{0}]: %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter-up-and-running/src/main/scala/com/goticks/EventMarshalling.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import spray.json._ 4 | 5 | case class EventDescription(tickets: Int) { 6 | require(tickets > 0) 7 | } 8 | 9 | case class TicketRequest(tickets: Int) { 10 | require(tickets > 0) 11 | } 12 | 13 | case class Error(message: String) 14 | 15 | 16 | trait EventMarshalling extends DefaultJsonProtocol { 17 | import BoxOffice._ 18 | 19 | implicit val eventDescriptionFormat = jsonFormat1(EventDescription) 20 | implicit val eventFormat = jsonFormat2(Event) 21 | implicit val eventsFormat = jsonFormat1(Events) 22 | implicit val ticketRequestFormat = jsonFormat1(TicketRequest) 23 | implicit val ticketFormat = jsonFormat1(TicketSeller.Ticket) 24 | implicit val ticketsFormat = jsonFormat2(TicketSeller.Tickets) 25 | implicit val errorFormat = jsonFormat1(Error) 26 | } 27 | -------------------------------------------------------------------------------- /chapter-up-and-running/src/main/scala/com/goticks/Main.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | 4 | import scala.concurrent.Future 5 | 6 | import akka.actor.ActorSystem 7 | import akka.event.Logging 8 | import akka.util.Timeout 9 | 10 | import akka.http.scaladsl.Http 11 | import akka.http.scaladsl.Http.ServerBinding 12 | import akka.stream.ActorMaterializer 13 | 14 | import com.typesafe.config.{ Config, ConfigFactory } 15 | import scala.util.{ Failure, Success } 16 | 17 | 18 | 19 | 20 | object Main extends App 21 | with RequestTimeout { 22 | 23 | val config = ConfigFactory.load() 24 | val host = config.getString("http.host") // 設定からホスト名とポートを取得 25 | val port = config.getInt("http.port") 26 | 27 | implicit val system = ActorSystem() 28 | implicit val ec = system.dispatcher // bindAndHandleは暗黙のExecutionContextが必要 29 | 30 | val api = new RestApi(system, requestTimeout(config)).routes // the RestApi provides a Route 31 | 32 | implicit val materializer = ActorMaterializer() 33 | val bindingFuture: Future[ServerBinding] = 34 | Http().bindAndHandle(api, host, port) // HTTPサーバーの起動 35 | 36 | val log = Logging(system.eventStream, "go-ticks") 37 | bindingFuture.map { serverBinding => 38 | log.info(s"RestApi bound to ${serverBinding.localAddress} ") 39 | }.onComplete { 40 | case Success(_) => 41 | log.info("Success to bind to {}:{}", host, port) 42 | case Failure(ex) => 43 | log.error(ex, "Failed to bind to {}:{}!", host, port) 44 | system.terminate() 45 | } 46 | } 47 | 48 | 49 | 50 | 51 | trait RequestTimeout { 52 | import scala.concurrent.duration._ 53 | def requestTimeout(config: Config): Timeout = { 54 | val t = config.getString("akka.http.server.request-timeout") 55 | val d = Duration(t) 56 | FiniteDuration(d.length, d.unit) 57 | } 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /chapter-up-and-running/src/main/scala/com/goticks/TicketSeller.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.actor.{ Actor, Props, PoisonPill } 4 | object TicketSeller { 5 | def props(event: String) = Props(new TicketSeller(event)) 6 | 7 | case class Add(tickets: Vector[Ticket]) 8 | case class Buy(tickets: Int) 9 | case class Ticket(id: Int) 10 | case class Tickets(event: String, 11 | entries: Vector[Ticket] = Vector.empty[Ticket]) 12 | case object GetEvent 13 | case object Cancel 14 | 15 | } 16 | 17 | 18 | class TicketSeller(event: String) extends Actor { 19 | import TicketSeller._ 20 | 21 | var tickets = Vector.empty[Ticket] 22 | 23 | def receive = { 24 | case Add(newTickets) => tickets = tickets ++ newTickets 25 | case Buy(nrOfTickets) => 26 | val entries = tickets.take(nrOfTickets) 27 | if(entries.size >= nrOfTickets) { 28 | sender() ! Tickets(event, entries) 29 | tickets = tickets.drop(nrOfTickets) 30 | } else sender() ! Tickets(event) 31 | case GetEvent => sender() ! Some(BoxOffice.Event(event, tickets.size)) 32 | case Cancel => 33 | sender() ! Some(BoxOffice.Event(event, tickets.size)) 34 | self ! PoisonPill 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /chapter-up-and-running/src/test/resources/application.conf: -------------------------------------------------------------------------------- 1 | include "../../main/resources/application" 2 | 3 | akka.loggers = [akka.testkit.TestEventListener] 4 | -------------------------------------------------------------------------------- /chapter-up-and-running/src/test/scala/com/goticks/StopSystemAfterAll.scala: -------------------------------------------------------------------------------- 1 | package com.goticks 2 | 3 | import akka.testkit.TestKit 4 | 5 | import org.scalatest.{Suite, BeforeAndAfterAll} 6 | 7 | trait StopSystemAfterAll extends BeforeAndAfterAll { 8 | this: TestKit with Suite => 9 | override protected def afterAll(): Unit = { 10 | super.afterAll() 11 | system.terminate() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.15 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0") 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0") 4 | 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.11") 6 | 7 | addSbtPlugin("com.typesafe.sbt" % "sbt-start-script" % "0.10.0") 8 | --------------------------------------------------------------------------------