move code to src/test
* so that it compiles and tests pass * fix some additional snip references in getting started
This commit is contained in:
parent
413df8e0f4
commit
59f53e1a22
289 changed files with 45 additions and 45 deletions
|
|
@ -18,8 +18,8 @@ paradoxProperties ++= Map(
|
||||||
"extref.wikipedia.base_url" -> "https://en.wikipedia.org/wiki/%s",
|
"extref.wikipedia.base_url" -> "https://en.wikipedia.org/wiki/%s",
|
||||||
"scala.version" -> scalaVersion.value,
|
"scala.version" -> scalaVersion.value,
|
||||||
"akka.version" -> version.value,
|
"akka.version" -> version.value,
|
||||||
"snip.code.base_dir" -> (sourceDirectory in Compile).value.getAbsolutePath,
|
"snip.code.base_dir" -> (sourceDirectory in Test).value.getAbsolutePath,
|
||||||
"snip.akka.base_dir" -> ((baseDirectory in Compile).value / "..").getAbsolutePath
|
"snip.akka.base_dir" -> ((baseDirectory in Test).value / "..").getAbsolutePath
|
||||||
)
|
)
|
||||||
|
|
||||||
resolvers += Resolver.bintrayRepo("2m", "maven")
|
resolvers += Resolver.bintrayRepo("2m", "maven")
|
||||||
|
|
|
||||||
|
|
@ -459,7 +459,7 @@ deterministically in the serialization.
|
||||||
|
|
||||||
This is a protobuf representation of the above `TwoPhaseSet`:
|
This is a protobuf representation of the above `TwoPhaseSet`:
|
||||||
|
|
||||||
@@snip [TwoPhaseSetMessages.proto]($code$/protobuf/TwoPhaseSetMessages.proto) { #twophaseset }
|
@@snip [TwoPhaseSetMessages.proto]($code$/../main/protobuf/TwoPhaseSetMessages.proto) { #twophaseset }
|
||||||
|
|
||||||
The serializer for the `TwoPhaseSet`:
|
The serializer for the `TwoPhaseSet`:
|
||||||
|
|
||||||
|
|
@ -481,7 +481,7 @@ The two embedded `GSet` can be serialized as illustrated above, but in general w
|
||||||
new data types from the existing built in types it is better to make use of the existing
|
new data types from the existing built in types it is better to make use of the existing
|
||||||
serializer for those types. This can be done by declaring those as bytes fields in protobuf:
|
serializer for those types. This can be done by declaring those as bytes fields in protobuf:
|
||||||
|
|
||||||
@@snip [TwoPhaseSetMessages.proto]($code$/protobuf/TwoPhaseSetMessages.proto) { #twophaseset2 }
|
@@snip [TwoPhaseSetMessages.proto]($code$/../main/protobuf/TwoPhaseSetMessages.proto) { #twophaseset2 }
|
||||||
|
|
||||||
and use the methods `otherMessageToProto` and `otherMessageFromBinary` that are provided
|
and use the methods `otherMessageToProto` and `otherMessageFromBinary` that are provided
|
||||||
by the `SerializationSupport` trait to serialize and deserialize the `GSet` instances. This
|
by the `SerializationSupport` trait to serialize and deserialize the `GSet` instances. This
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@ Next we prepare an protocol definition using the protobuf Interface Description
|
||||||
the serializer code to be used on the Akka Serialization layer (notice that the schema aproach allows us to easily rename
|
the serializer code to be used on the Akka Serialization layer (notice that the schema aproach allows us to easily rename
|
||||||
fields, as long as the numeric identifiers of the fields do not change):
|
fields, as long as the numeric identifiers of the fields do not change):
|
||||||
|
|
||||||
@@snip [FlightAppModels.proto]($code$/protobuf/FlightAppModels.proto) { #protobuf-read-optional-proto }
|
@@snip [FlightAppModels.proto]($code$/../main/protobuf/FlightAppModels.proto) { #protobuf-read-optional-proto }
|
||||||
|
|
||||||
The serializer implementation uses the protobuf generated classes to marshall the payloads.
|
The serializer implementation uses the protobuf generated classes to marshall the payloads.
|
||||||
Optional fields can be handled explicitly or missing values by calling the `has...` methods on the protobuf object,
|
Optional fields can be handled explicitly or missing values by calling the `has...` methods on the protobuf object,
|
||||||
|
|
|
||||||
|
|
@ -471,7 +471,7 @@ deterministically in the serialization.
|
||||||
|
|
||||||
This is a protobuf representation of the above `TwoPhaseSet`:
|
This is a protobuf representation of the above `TwoPhaseSet`:
|
||||||
|
|
||||||
@@snip [TwoPhaseSetMessages.proto]($code$/protobuf/TwoPhaseSetMessages.proto) { #twophaseset }
|
@@snip [TwoPhaseSetMessages.proto]($code$/../main/protobuf/TwoPhaseSetMessages.proto) { #twophaseset }
|
||||||
|
|
||||||
The serializer for the `TwoPhaseSet`:
|
The serializer for the `TwoPhaseSet`:
|
||||||
|
|
||||||
|
|
@ -493,7 +493,7 @@ The two embedded `GSet` can be serialized as illustrated above, but in general w
|
||||||
new data types from the existing built in types it is better to make use of the existing
|
new data types from the existing built in types it is better to make use of the existing
|
||||||
serializer for those types. This can be done by declaring those as bytes fields in protobuf:
|
serializer for those types. This can be done by declaring those as bytes fields in protobuf:
|
||||||
|
|
||||||
@@snip [TwoPhaseSetMessages.proto]($code$/protobuf/TwoPhaseSetMessages.proto) { #twophaseset2 }
|
@@snip [TwoPhaseSetMessages.proto]($code$/../main/protobuf/TwoPhaseSetMessages.proto) { #twophaseset2 }
|
||||||
|
|
||||||
and use the methods `otherMessageToProto` and `otherMessageFromBinary` that are provided
|
and use the methods `otherMessageToProto` and `otherMessageFromBinary` that are provided
|
||||||
by the `SerializationSupport` trait to serialize and deserialize the `GSet` instances. This
|
by the `SerializationSupport` trait to serialize and deserialize the `GSet` instances. This
|
||||||
|
|
|
||||||
|
|
@ -111,14 +111,14 @@ The usual pattern is to have your system set up to stop on external signal (i.e.
|
||||||
|
|
||||||
Once there is an `ActorSystem` we can populate it with actors. This is done by using the `actorOf` method. The `actorOf` method expects a `Props` instance and the name of the actor to be created. You can think of the `Props` as a configuration value for what actor to create and how it should be created. Creating an actor with the `actorOf` method will return an `ActorRef` instance. Think of the `ActorRef` as a unique address with which it is possible to message the actor instance. The `ActorRef` object contains a few methods with which you can send messages to the actor instance. One of them is called `tell`, or in the Scala case simply `!` (bang), and this method is used in the example here below. Calling the `!` method is an asynchronous operation and it instructs Akka to send a message to the actor instance that is uniquely identified by the actor reference.
|
Once there is an `ActorSystem` we can populate it with actors. This is done by using the `actorOf` method. The `actorOf` method expects a `Props` instance and the name of the actor to be created. You can think of the `Props` as a configuration value for what actor to create and how it should be created. Creating an actor with the `actorOf` method will return an `ActorRef` instance. Think of the `ActorRef` as a unique address with which it is possible to message the actor instance. The `ActorRef` object contains a few methods with which you can send messages to the actor instance. One of them is called `tell`, or in the Scala case simply `!` (bang), and this method is used in the example here below. Calling the `!` method is an asynchronous operation and it instructs Akka to send a message to the actor instance that is uniquely identified by the actor reference.
|
||||||
|
|
||||||
@@snip [HelloWorldApp.scala](../../../../test/scala/quickstart/HelloWorldApp.scala) { #create-send }
|
@@snip [HelloWorldApp.scala]($code$/scala/quickstart/HelloWorldApp.scala) { #create-send }
|
||||||
|
|
||||||
Before we can create any actor in the actor system we must define one first. Luckily, creating actors in Akka is quite simple! Just have your actor class extend `akka.actor.Actor` and override the method `receive: Receive` and you are good to go. As for our `HelloWorldActor` class, it extends `Actor` and overrides the `receive` method as per the requirement. Our implementation of the `receive` method expects messages of type `String`. For every `String` message it receives it will print "Hello " and the value of the `String`. Since the message we send in the main class is "World" we expect the string "Hello World" to be printed when running the application.
|
Before we can create any actor in the actor system we must define one first. Luckily, creating actors in Akka is quite simple! Just have your actor class extend `akka.actor.Actor` and override the method `receive: Receive` and you are good to go. As for our `HelloWorldActor` class, it extends `Actor` and overrides the `receive` method as per the requirement. Our implementation of the `receive` method expects messages of type `String`. For every `String` message it receives it will print "Hello " and the value of the `String`. Since the message we send in the main class is "World" we expect the string "Hello World" to be printed when running the application.
|
||||||
|
|
||||||
@@snip [HelloWorldApp.scala](../../../../test/scala/quickstart/HelloWorldApp.scala) { #actor-impl }
|
@@snip [HelloWorldApp.scala]($code$/scala/quickstart/HelloWorldApp.scala) { #actor-impl }
|
||||||
|
|
||||||
Here is the full example:
|
Here is the full example:
|
||||||
|
|
||||||
@@snip [HelloWorldApp.scala](../../../../test/scala/quickstart/HelloWorldApp.scala) { #full-example }
|
@@snip [HelloWorldApp.scala]($code$/scala/quickstart/HelloWorldApp.scala) { #full-example }
|
||||||
|
|
||||||
Now that you have seen the basics of an Akka application it is time to dive deeper.
|
Now that you have seen the basics of an Akka application it is time to dive deeper.
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ convenient terminology, and we will stick to it.
|
||||||
Creating a non-top-level actor is possible from any actor, by invoking `context.actorOf()` which has the exact same
|
Creating a non-top-level actor is possible from any actor, by invoking `context.actorOf()` which has the exact same
|
||||||
signature as its top-level counterpart. This is how it looks like in practice:
|
signature as its top-level counterpart. This is how it looks like in practice:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_1/ActorHierarchyExperiments.scala) { #print-refs }
|
@@snip [Hello.scala]($code$/scala/tutorial_1/ActorHierarchyExperiments.scala) { #print-refs }
|
||||||
|
|
||||||
We see that the following two lines are printed
|
We see that the following two lines are printed
|
||||||
|
|
||||||
|
|
@ -126,7 +126,7 @@ The actor API exposes many lifecycle hooks that the actor implementation can ove
|
||||||
|
|
||||||
Again, we can try out all this with a simple experiment:
|
Again, we can try out all this with a simple experiment:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_1/ActorHierarchyExperiments.scala) { #start-stop }
|
@@snip [Hello.scala]($code$/scala/tutorial_1/ActorHierarchyExperiments.scala) { #start-stop }
|
||||||
|
|
||||||
After running it, we get the output
|
After running it, we get the output
|
||||||
|
|
||||||
|
|
@ -151,7 +151,7 @@ to the parent, which decides how to handle the exception caused by the child act
|
||||||
stop and restart the child. If you don't change the default strategy all failures result in a restart. We won't change
|
stop and restart the child. If you don't change the default strategy all failures result in a restart. We won't change
|
||||||
the default strategy in this simple experiment:
|
the default strategy in this simple experiment:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_1/ActorHierarchyExperiments.scala) { #supervise }
|
@@snip [Hello.scala]($code$/scala/tutorial_1/ActorHierarchyExperiments.scala) { #supervise }
|
||||||
|
|
||||||
After running the snippet, we see the following output on the console:
|
After running the snippet, we see the following output on the console:
|
||||||
|
|
||||||
|
|
@ -211,11 +211,11 @@ The first actor happens to be rather simple now, as we have not implemented any
|
||||||
is that we have dropped using `println()` and instead use the `ActorLogging` helper trait which allows us to use the
|
is that we have dropped using `println()` and instead use the `ActorLogging` helper trait which allows us to use the
|
||||||
logging facility built into Akka directly. Furthermore, we are using a recommended creational pattern for actors; define a `props()` method in the [companion object](http://docs.scala-lang.org/tutorials/tour/singleton-objects.html#companions) of the actor:
|
logging facility built into Akka directly. Furthermore, we are using a recommended creational pattern for actors; define a `props()` method in the [companion object](http://docs.scala-lang.org/tutorials/tour/singleton-objects.html#companions) of the actor:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_1/IotSupervisor.scala) { #iot-supervisor }
|
@@snip [Hello.scala]($code$/scala/tutorial_1/IotSupervisor.scala) { #iot-supervisor }
|
||||||
|
|
||||||
All we need now is to tie this up with a class with the `main` entry point:
|
All we need now is to tie this up with a class with the `main` entry point:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_1/IotApp.scala) { #iot-app }
|
@@snip [Hello.scala]($code$/scala/tutorial_1/IotApp.scala) { #iot-app }
|
||||||
|
|
||||||
This application does very little for now, but we have the first actor in place and we are ready to extend it further.
|
This application does very little for now, but we have the first actor in place and we are ready to extend it further.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ The protocol for obtaining the current temperature from the device actor is rath
|
||||||
|
|
||||||
We need two messages, one for the request, and one for the reply. A first attempt could look like this:
|
We need two messages, one for the request, and one for the reply. A first attempt could look like this:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_2/DeviceInProgress.scala) { #read-protocol-1 }
|
@@snip [Hello.scala]($code$/scala/tutorial_2/DeviceInProgress.scala) { #read-protocol-1 }
|
||||||
|
|
||||||
This is a fine approach, but it limits the flexibility of the protocol. To understand why we need to talk
|
This is a fine approach, but it limits the flexibility of the protocol. To understand why we need to talk
|
||||||
about message ordering and message delivery guarantees in general.
|
about message ordering and message delivery guarantees in general.
|
||||||
|
|
@ -139,12 +139,12 @@ can be helpful to put an additional query ID field in the message which helps us
|
||||||
|
|
||||||
Hence, we add one more field to our messages, so that an ID can be provided by the requester:
|
Hence, we add one more field to our messages, so that an ID can be provided by the requester:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_2/DeviceInProgress.scala) { #read-protocol-2 }
|
@@snip [Hello.scala]($code$/scala/tutorial_2/DeviceInProgress.scala) { #read-protocol-2 }
|
||||||
|
|
||||||
Our device actor has the responsibility to use the same ID for the response of a given query. Now we can sketch
|
Our device actor has the responsibility to use the same ID for the response of a given query. Now we can sketch
|
||||||
our device actor:
|
our device actor:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_2/DeviceInProgress.scala) { #device-with-read }
|
@@snip [Hello.scala]($code$/scala/tutorial_2/DeviceInProgress.scala) { #device-with-read }
|
||||||
|
|
||||||
We maintain the current temperature, initially set to `None`, and we simply report it back if queried. We also
|
We maintain the current temperature, initially set to `None`, and we simply report it back if queried. We also
|
||||||
added fields for the ID of the device and the group it belongs to, which we will use later.
|
added fields for the ID of the device and the group it belongs to, which we will use later.
|
||||||
|
|
@ -152,7 +152,7 @@ added fields for the ID of the device and the group it belongs to, which we will
|
||||||
We can already write a simple test for this functionality (we use ScalaTest but any other test framework can be
|
We can already write a simple test for this functionality (we use ScalaTest but any other test framework can be
|
||||||
used with the Akka Testkit):
|
used with the Akka Testkit):
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_2/DeviceSpec.scala) { #device-read-test }
|
@@snip [Hello.scala]($code$/scala/tutorial_2/DeviceSpec.scala) { #device-read-test }
|
||||||
|
|
||||||
## The Write Protocol
|
## The Write Protocol
|
||||||
|
|
||||||
|
|
@ -162,7 +162,7 @@ As a first attempt, we could model recording the current temperature in the devi
|
||||||
|
|
||||||
Such a message could possibly look like this:
|
Such a message could possibly look like this:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_2/DeviceInProgress.scala) { #write-protocol-1 }
|
@@snip [Hello.scala]($code$/scala/tutorial_2/DeviceInProgress.scala) { #write-protocol-1 }
|
||||||
|
|
||||||
The problem with this approach is that the sender of the record temperature message can never be sure if the message
|
The problem with this approach is that the sender of the record temperature message can never be sure if the message
|
||||||
was processed or not. We have seen that Akka does not guarantee delivery of these messages and leaves it to the
|
was processed or not. We have seen that Akka does not guarantee delivery of these messages and leaves it to the
|
||||||
|
|
@ -172,12 +172,12 @@ Just like in the case of temperature queries and responses, it is a good idea to
|
||||||
|
|
||||||
Putting read and write protocol together, the device actor will look like this:
|
Putting read and write protocol together, the device actor will look like this:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_2/Device.scala) { #full-device }
|
@@snip [Hello.scala]($code$/scala/tutorial_2/Device.scala) { #full-device }
|
||||||
|
|
||||||
We are also responsible for writing a new test case now, exercising both the read/query and write/record functionality
|
We are also responsible for writing a new test case now, exercising both the read/query and write/record functionality
|
||||||
together:
|
together:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_2/DeviceSpec.scala) { #device-write-read-test }
|
@@snip [Hello.scala]($code$/scala/tutorial_2/DeviceSpec.scala) { #device-write-read-test }
|
||||||
|
|
||||||
## What is Next?
|
## What is Next?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ is known up front: device groups and device actors are created on-demand. The st
|
||||||
Now that the steps are defined, we only need to define the messages that we will use to communicate requests and
|
Now that the steps are defined, we only need to define the messages that we will use to communicate requests and
|
||||||
their acknowledgement:
|
their acknowledgement:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_3/DeviceManager.scala) { #device-manager-msgs }
|
@@snip [Hello.scala]($code$/scala/tutorial_3/DeviceManager.scala) { #device-manager-msgs }
|
||||||
|
|
||||||
As you see, in this case, we have not included a request ID field in the messages. Since registration is usually happening
|
As you see, in this case, we have not included a request ID field in the messages. Since registration is usually happening
|
||||||
once, at the component that connects the system to some network protocol, we will usually have no use for the ID.
|
once, at the component that connects the system to some network protocol, we will usually have no use for the ID.
|
||||||
|
|
@ -104,7 +104,7 @@ the code looks like:
|
||||||
value. This is achieved by variables included in backticks, like `` `variable` ``, and it means that the pattern
|
value. This is achieved by variables included in backticks, like `` `variable` ``, and it means that the pattern
|
||||||
only match if it contains the value of `variable` in that position.
|
only match if it contains the value of `variable` in that position.
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_3/Device.scala) { #device-with-register }
|
@@snip [Hello.scala]($code$/scala/tutorial_3/Device.scala) { #device-with-register }
|
||||||
|
|
||||||
We should not leave features untested, so we immediately write two new test cases, one exercising successful
|
We should not leave features untested, so we immediately write two new test cases, one exercising successful
|
||||||
registration, the other testing the case when IDs don't match:
|
registration, the other testing the case when IDs don't match:
|
||||||
|
|
@ -114,7 +114,7 @@ and fails if it receives any messages during this period. If no messages are rec
|
||||||
assertion passes. It is usually a good idea to keep these timeouts low (but not too low) because they add significant
|
assertion passes. It is usually a good idea to keep these timeouts low (but not too low) because they add significant
|
||||||
test execution time otherwise.
|
test execution time otherwise.
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_3/DeviceSpec.scala) { #device-registration-tests }
|
@@snip [Hello.scala]($code$/scala/tutorial_3/DeviceSpec.scala) { #device-registration-tests }
|
||||||
|
|
||||||
## Device Group
|
## Device Group
|
||||||
|
|
||||||
|
|
@ -127,18 +127,18 @@ by using `forward` instead of the `!` operator. The only difference between the
|
||||||
sender while `!` always sets the sender to be the current actor. Just like with our device actor, we ensure that we don't
|
sender while `!` always sets the sender to be the current actor. Just like with our device actor, we ensure that we don't
|
||||||
respond to wrong group IDs:
|
respond to wrong group IDs:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_3/DeviceGroup.scala) { #device-group-register }
|
@@snip [Hello.scala]($code$/scala/tutorial_3/DeviceGroup.scala) { #device-group-register }
|
||||||
|
|
||||||
Just as we did with the device, we test this new functionality. We also test that the actors returned for the two
|
Just as we did with the device, we test this new functionality. We also test that the actors returned for the two
|
||||||
different IDs are actually different, and we also attempt to record a temperature reading for each of the devices
|
different IDs are actually different, and we also attempt to record a temperature reading for each of the devices
|
||||||
to see if the actors are responding.
|
to see if the actors are responding.
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_3/DeviceGroupSpec.scala) { #device-group-test-registration }
|
@@snip [Hello.scala]($code$/scala/tutorial_3/DeviceGroupSpec.scala) { #device-group-test-registration }
|
||||||
|
|
||||||
It might be, that a device actor already exists for the registration request. In this case, we would like to use
|
It might be, that a device actor already exists for the registration request. In this case, we would like to use
|
||||||
the existing actor instead of a new one. We have not tested this yet, so we need to fix this:
|
the existing actor instead of a new one. We have not tested this yet, so we need to fix this:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_3/DeviceGroupSpec.scala) { #device-group-test3 }
|
@@snip [Hello.scala]($code$/scala/tutorial_3/DeviceGroupSpec.scala) { #device-group-test3 }
|
||||||
|
|
||||||
So far, we have implemented everything for registering device actors in the group. Devices come and go, however, so
|
So far, we have implemented everything for registering device actors in the group. Devices come and go, however, so
|
||||||
we will need a way to remove those from the `Map[String, ActorRef]`. We will assume that when a device is removed, its corresponding device actor
|
we will need a way to remove those from the `Map[String, ActorRef]`. We will assume that when a device is removed, its corresponding device actor
|
||||||
|
|
@ -163,13 +163,13 @@ its ID, which we need to remove it from the map of existing device to device act
|
||||||
need to introduce another placeholder, `Map[ActorRef, String]`, that allow us to find out the device ID corresponding to a given `ActorRef`. Putting
|
need to introduce another placeholder, `Map[ActorRef, String]`, that allow us to find out the device ID corresponding to a given `ActorRef`. Putting
|
||||||
this together the result is:
|
this together the result is:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_3/DeviceGroup.scala) { #device-group-remove }
|
@@snip [Hello.scala]($code$/scala/tutorial_3/DeviceGroup.scala) { #device-group-remove }
|
||||||
|
|
||||||
So far we have no means to get what devices the group device actor keeps track of and, therefore, we cannot test our
|
So far we have no means to get what devices the group device actor keeps track of and, therefore, we cannot test our
|
||||||
new functionality yet. To make it testable, we add a new query capability (message `RequestDeviceList(requestId: Long)`) that simply lists the currently active
|
new functionality yet. To make it testable, we add a new query capability (message `RequestDeviceList(requestId: Long)`) that simply lists the currently active
|
||||||
device IDs:
|
device IDs:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_3/DeviceGroup.scala) { #device-group-full }
|
@@snip [Hello.scala]($code$/scala/tutorial_3/DeviceGroup.scala) { #device-group-full }
|
||||||
|
|
||||||
We almost have everything to test the removal of devices. What is missing is:
|
We almost have everything to test the removal of devices. What is missing is:
|
||||||
|
|
||||||
|
|
@ -183,14 +183,14 @@ We add two more test cases now. In the first, we just test that we get back the
|
||||||
a few devices. The second test case makes sure that the device ID is properly removed after the device actor has
|
a few devices. The second test case makes sure that the device ID is properly removed after the device actor has
|
||||||
been stopped:
|
been stopped:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_3/DeviceGroupSpec.scala) { #device-group-list-terminate-test }
|
@@snip [Hello.scala]($code$/scala/tutorial_3/DeviceGroupSpec.scala) { #device-group-list-terminate-test }
|
||||||
|
|
||||||
## Device Manager
|
## Device Manager
|
||||||
|
|
||||||
The only part that remains now is the entry point for our device manager component. This actor is very similar to
|
The only part that remains now is the entry point for our device manager component. This actor is very similar to
|
||||||
the device group actor, with the only difference that it creates device group actors instead of device actors:
|
the device group actor, with the only difference that it creates device group actors instead of device actors:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_3/DeviceManager.scala) { #device-manager-full }
|
@@snip [Hello.scala]($code$/scala/tutorial_3/DeviceManager.scala) { #device-manager-full }
|
||||||
|
|
||||||
We leave tests of the device manager as an exercise as it is very similar to the tests we have written for the group
|
We leave tests of the device manager as an exercise as it is very similar to the tests we have written for the group
|
||||||
actor.
|
actor.
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ that each device can be in, according to the query:
|
||||||
|
|
||||||
Summarizing these in message types we can add the following to `DeviceGroup`:
|
Summarizing these in message types we can add the following to `DeviceGroup`:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_4/DeviceGroup.scala) { #query-protocol }
|
@@snip [Hello.scala]($code$/scala/tutorial_4/DeviceGroup.scala) { #query-protocol }
|
||||||
|
|
||||||
## Implementing the Query
|
## Implementing the Query
|
||||||
|
|
||||||
|
|
@ -87,7 +87,7 @@ until the timeout to mark these as not available.
|
||||||
|
|
||||||
Putting together all these, the outline of our actor looks like this:
|
Putting together all these, the outline of our actor looks like this:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_4/DeviceGroupQuery.scala) { #query-outline }
|
@@snip [Hello.scala]($code$/scala/tutorial_4/DeviceGroupQuery.scala) { #query-outline }
|
||||||
|
|
||||||
The query actor, apart from the pending timer, has one stateful aspect about it: the actors that did not answer so far or,
|
The query actor, apart from the pending timer, has one stateful aspect about it: the actors that did not answer so far or,
|
||||||
from the other way around, the set of actors that have replied or stopped. One way to track this state is
|
from the other way around, the set of actors that have replied or stopped. One way to track this state is
|
||||||
|
|
@ -108,7 +108,7 @@ we will discuss later. In the case of timeout, we need to simply take all the ac
|
||||||
(the members of the set `stillWaiting`) and put a `DeviceTimedOut` as the status in the final reply. Then we
|
(the members of the set `stillWaiting`) and put a `DeviceTimedOut` as the status in the final reply. Then we
|
||||||
reply to the submitter of the query with the collected results and stop the query actor:
|
reply to the submitter of the query with the collected results and stop the query actor:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_4/DeviceGroupQuery.scala) { #query-state }
|
@@snip [Hello.scala]($code$/scala/tutorial_4/DeviceGroupQuery.scala) { #query-state }
|
||||||
|
|
||||||
What is not yet clear, how we will "mutate" the `answersSoFar` and `stillWaiting` data structures. One important
|
What is not yet clear, how we will "mutate" the `answersSoFar` and `stillWaiting` data structures. One important
|
||||||
thing to note is that the function `waitingForReplies` **does not handle the messages directly. It returns a `Receive`
|
thing to note is that the function `waitingForReplies` **does not handle the messages directly. It returns a `Receive`
|
||||||
|
|
@ -137,7 +137,7 @@ only the first call will have any effect, the rest is simply ignored.
|
||||||
|
|
||||||
With all this knowledge, we can create the `receivedResponse` method:
|
With all this knowledge, we can create the `receivedResponse` method:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_4/DeviceGroupQuery.scala) { #query-collect-reply }
|
@@snip [Hello.scala]($code$/scala/tutorial_4/DeviceGroupQuery.scala) { #query-collect-reply }
|
||||||
|
|
||||||
It is quite natural to ask at this point, what have we gained by using the `context.become()` trick instead of
|
It is quite natural to ask at this point, what have we gained by using the `context.become()` trick instead of
|
||||||
just making the `repliesSoFar` and `stillWaiting` structures mutable fields of the actor (i.e. `var`s)? In this
|
just making the `repliesSoFar` and `stillWaiting` structures mutable fields of the actor (i.e. `var`s)? In this
|
||||||
|
|
@ -151,7 +151,7 @@ with the solution we have used here as it helps structuring more complex actor c
|
||||||
|
|
||||||
Or query actor is now done:
|
Or query actor is now done:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_4/DeviceGroupQuery.scala) { #query-full }
|
@@snip [Hello.scala]($code$/scala/tutorial_4/DeviceGroupQuery.scala) { #query-full }
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
|
|
@ -161,27 +161,27 @@ various normal or failure scenarios. Thankfully we took the list of collaborator
|
||||||
to the query actor, so we can easily pass in `TestProbe` references. In our first test, we try out the case when
|
to the query actor, so we can easily pass in `TestProbe` references. In our first test, we try out the case when
|
||||||
there are two devices and both report a temperature:
|
there are two devices and both report a temperature:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-normal }
|
@@snip [Hello.scala]($code$/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-normal }
|
||||||
|
|
||||||
That was the happy case, but we know that sometimes devices cannot provide a temperature measurement. This
|
That was the happy case, but we know that sometimes devices cannot provide a temperature measurement. This
|
||||||
scenario is just slightly different from the previous:
|
scenario is just slightly different from the previous:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-no-reading }
|
@@snip [Hello.scala]($code$/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-no-reading }
|
||||||
|
|
||||||
We also know, that sometimes device actors stop before answering:
|
We also know, that sometimes device actors stop before answering:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-stopped }
|
@@snip [Hello.scala]($code$/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-stopped }
|
||||||
|
|
||||||
If you remember, there is another case related to device actors stopping. It is possible that we get a normal reply
|
If you remember, there is another case related to device actors stopping. It is possible that we get a normal reply
|
||||||
from a device actor, but then receive a `Terminated` for the same actor later. In this case, we would like to keep
|
from a device actor, but then receive a `Terminated` for the same actor later. In this case, we would like to keep
|
||||||
the first reply and not mark the device as `DeviceNotAvailable`. We should test this, too:
|
the first reply and not mark the device as `DeviceNotAvailable`. We should test this, too:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-stopped-later }
|
@@snip [Hello.scala]($code$/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-stopped-later }
|
||||||
|
|
||||||
The final case is when not all devices respond in time. To keep our test relatively fast, we will construct the
|
The final case is when not all devices respond in time. To keep our test relatively fast, we will construct the
|
||||||
`DeviceGroupQuery` actor with a smaller timeout:
|
`DeviceGroupQuery` actor with a smaller timeout:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-timeout }
|
@@snip [Hello.scala]($code$/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-timeout }
|
||||||
|
|
||||||
Our query works as expected now, it is time to include this new functionality in the `DeviceGroup` actor now.
|
Our query works as expected now, it is time to include this new functionality in the `DeviceGroup` actor now.
|
||||||
|
|
||||||
|
|
@ -190,7 +190,7 @@ Our query works as expected now, it is time to include this new functionality in
|
||||||
Including the query feature in the group actor is fairly simple now. We did all the heavy lifting in the query actor
|
Including the query feature in the group actor is fairly simple now. We did all the heavy lifting in the query actor
|
||||||
itself, the group actor only needs to create it with the right initial parameters and nothing else.
|
itself, the group actor only needs to create it with the right initial parameters and nothing else.
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_4/DeviceGroup.scala) { #query-added }
|
@@snip [Hello.scala]($code$/scala/tutorial_4/DeviceGroup.scala) { #query-added }
|
||||||
|
|
||||||
It is probably worth to reiterate what we said at the beginning of the chapter. By keeping the temporary state
|
It is probably worth to reiterate what we said at the beginning of the chapter. By keeping the temporary state
|
||||||
that is only relevant to the query itself in a separate actor we keep the group actor implementation very simple. It delegates
|
that is only relevant to the query itself in a separate actor we keep the group actor implementation very simple. It delegates
|
||||||
|
|
@ -202,4 +202,4 @@ would significantly improve throughput.
|
||||||
We close this chapter by testing that everything works together. This test is just a variant of the previous ones,
|
We close this chapter by testing that everything works together. This test is just a variant of the previous ones,
|
||||||
now exercising the group query feature:
|
now exercising the group query feature:
|
||||||
|
|
||||||
@@snip [Hello.scala](../../../../test/scala/tutorial_4/DeviceGroupSpec.scala) { #group-query-integration-test }
|
@@snip [Hello.scala]($code$/scala/tutorial_4/DeviceGroupSpec.scala) { #group-query-integration-test }
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@ Next we prepare an protocol definition using the protobuf Interface Description
|
||||||
the serializer code to be used on the Akka Serialization layer (notice that the schema aproach allows us to easily rename
|
the serializer code to be used on the Akka Serialization layer (notice that the schema aproach allows us to easily rename
|
||||||
fields, as long as the numeric identifiers of the fields do not change):
|
fields, as long as the numeric identifiers of the fields do not change):
|
||||||
|
|
||||||
@@snip [FlightAppModels.proto]($code$/protobuf/FlightAppModels.proto) { #protobuf-read-optional-proto }
|
@@snip [FlightAppModels.proto]($code$/../main/protobuf/FlightAppModels.proto) { #protobuf-read-optional-proto }
|
||||||
|
|
||||||
The serializer implementation uses the protobuf generated classes to marshall the payloads.
|
The serializer implementation uses the protobuf generated classes to marshall the payloads.
|
||||||
Optional fields can be handled explicitly or missing values by calling the `has...` methods on the protobuf object,
|
Optional fields can be handled explicitly or missing values by calling the `has...` methods on the protobuf object,
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue