├── .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 |
--------------------------------------------------------------------------------