Added Hello World example, rephrased some wording, corrected spelling and grammatical errors, completed missing links, and misc other improvements.

This commit is contained in:
henrikengstrom 2017-04-07 13:39:25 -04:00
parent 2616117476
commit 348ae4b50e
22 changed files with 762 additions and 743 deletions

View file

@ -15,6 +15,8 @@ disablePlugins(MimaPlugin)
enablePlugins(ParadoxPlugin)
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,
"akka.version" -> version.value
)
paradoxTheme := Some(builtinParadoxTheme("generic"))

View file

@ -7,14 +7,14 @@ computing.
### The illusion of encapsulation
Object oriented programming (OOP) is a widely-accepted, familiar programming model. One of its basic pillars is
Object-oriented programming (OOP) is a widely-accepted, familiar programming model. One of its core pillars is
_encapsulation_. Encapsulation dictates that the internal data of an object is not accessible directly from the outside;
it can only modified by invoking a set of curated methods. The object is responsible to expose only safe operations
it can only be modified by invoking a set of curated methods. The object is responsible for exposing safe operations
that protect the invariant nature of its encapsulated data.
For example, operations on an ordered binary tree implementation must not allow violation of the tree ordering
invariant. Callers rely on the ordering being intact. For example, when querying the tree for a certain piece of
data, you need to be able to rely on this constraint.
invariant. Callers expect the ordering to be intact and when querying the tree for a certain piece of
data, they need to be able to rely on this constraint.
When we analyze OOP runtime behavior, we sometimes draw a message sequence chart showing the interactions of
method calls. For example:
@ -34,25 +34,24 @@ the same instance:
![sequence chart with threads interacting](diagrams/seq_chart_multi_thread.png)
There is a section of execution where two threads enter the same method. Unfortunately, the encapsulation model
of objects does not guarantee anything about what happens in that section. In fact, we also see one thread calling
into the first object while it is in the middle of a call to a third one. Instructions of the two invocations
can be interleaved in arbitrary ways which eliminates any hope for keeping the invariants intact without some
of objects does not guarantee anything about what happens in that section. Instructions of the two invocations
can be interleaved in arbitrary ways which eliminate any hope for keeping the invariants intact without some
type of coordination between two threads. Now, imagine this issue compounded by the existence of many threads.
The common approach for solving this problem is to add a lock around these methods. While this ensures that at most
The common approach to solving this problem is to add a lock around these methods. While this ensures that at most
one thread will enter the method at any given time, this is a very costly strategy:
* Locks _seriously limit_ concurrency, they are very costly on modern CPU architectures,
requiring heavy-lifting from the operating system to suspend the thread and restore it later.
* The caller thread is now blocked, so it cannot do any other meaningful work. Even in desktop applications this is
unacceptable, we want to keep user facing parts of an applications (its UI) to be responsive even when a
long background job is running. In backend, server settings, this is outright wasteful.
unacceptable, we want to keep user-facing parts of applications (its UI) to be responsive even when a
long background job is running. In the backend, blocking is outright wasteful.
One might think that this can be compensated by launching new threads, but threads are also a costly abstraction.
* Locks introduce a new menace: deadlocks.
These realities result in a no-win situation:
* Without sufficient locks, state gets corrupted.
* Without sufficient locks, the state gets corrupted.
* With many locks in place, performance suffers and very easily leads to deadlocks.
Additionally, locks only really work well locally. When it comes to coordinating across multiple machines,
@ -60,14 +59,13 @@ the only alternative is distributed locks. Unfortunately, distributed locks are
than local locks and usually impose a hard limit on scaling out. Distributed lock protocols require several
communication round-trips over the network across multiple machines, so latency goes through the roof.
In Object Oriented languages we rarely think about threads, or linear execution paths in general.
In Object Oriented languages we rarely think about threads or linear execution paths in general.
We often envision a system as a network of object instances that react to method calls, modify their internal state,
then communicate with each other via method calls driving the whole application state forward:
![network of interacting objects](diagrams/object_graph.png)
However, in a multi-threaded distributed environment, what actually happens is more like the game Snake,
in the sense that threads "traverse" this network of object instances by following method calls.
However, in a multi-threaded distributed environment, what actually happens is that threads "traverse" this network of object instances by following method calls.
As a result, threads are what really drive execution:
![network of interactive objects traversed by threads](diagrams/object_graph_snakes.png)
@ -76,7 +74,7 @@ As a result, threads are what really drive execution:
* **Objects can only guarantee encapsulation (protection of invariants) in the face of single-threaded access,
multi-thread execution almost always leads to corrupted internal state. Every invariant can be violated by
having two contending threads on the same code segment**
having two contending threads in the same code segment.**
* **While locks seem to be the natural remedy to uphold encapsulation with multiple threads, in practice they
are inefficient and easily lead to deadlocks in any application of real-world scale.**
* **Locks work locally, attempts to make them distributed exist, but offer limited potential for scaling out.**
@ -84,16 +82,16 @@ As a result, threads are what really drive execution:
### The illusion of shared memory on modern computer architectures
Programming models of the 80'-90's conceptualize that writing to a variable means writing to a memory location directly
(somewhat muddies the water that local variables might exist only in registers). On modern architectures -
(which somewhat muddies the water that local variables might exist only in registers). On modern architectures -
if we simplify things a bit - CPUs are writing to [cache lines](https://en.wikipedia.org/wiki/CPU_cache)
instead of writing to memory directly. Most of these caches are local to the CPU core, that is, writes by one core
are not visible by another. In order to make local changes visible to another core (and hence, to another thread)
the cache line needs to be shipped to the other processor's cache.
are not visible by another. In order to make local changes visible to another core, and hence to another thread,
the cache line needs to be shipped to the other core's cache.
On the JVM, we have to explicitly denote memory locations to be shared across threads by using _volatile_ markers
or `Atomic` wrappers. Otherwise, we can access them only in a locked section. Why can't we just mark all variables as
volatile? Because shipping cache lines across cores is a very costly operation. It implicitly stalls the cores
involved from doing additional work, and results in bottlenecks on the cache coherence protocol (the protocol CPUs
or `Atomic` wrappers. Otherwise, we can access them only in a locked section. Why don't we just mark all variables as
volatile? Because shipping cache lines across cores is a very costly operation! Doing so would implicitly stall the cores
involved from doing additional work, and result in bottlenecks on the cache coherence protocol (the protocol CPUs
use to transfer cache lines between main memory and other CPUs).
The result is magnitudes of slowdown.
@ -103,8 +101,7 @@ or which atomic structures to use is a dark art.
**In summary**:
* **There is no real shared memory anymore, CPU cores pass chunks of data (cache lines) explicitly to each other
just as computers on a network do. Inter-CPU communication and network communication have more in common than
many realize. Passing messages is the norm now be it across CPUs or networked computers.**
just as computers on a network do. Inter-CPU communication and network communication have more in common than many realize. Passing messages is the norm now be it across CPUs or networked computers.**
* **Instead of hiding the message passing aspect through variables marked as shared or using atomic data structures,
a more disciplined and principled approach is to keep state local to a concurrent entity and propagate data or events
between concurrent entities explicitly via messages.**
@ -112,7 +109,7 @@ or which atomic structures to use is a dark art.
### The illusion of a call stack
Today, we often take call stacks for granted. But, they were invented in an era where concurrent programming
was not as important because multi-CPU systems were not common. Call stacks do not span threads and hence,
was not as important because multi-CPU systems were not common. Call stacks do not cross threads and hence,
do not model asynchronous call chains.
The problem arises when a thread intends to delegate a task to the "background". In practice, this really means
@ -128,28 +125,28 @@ of the worker thread completely ignoring who the actual "caller" was:
This is a serious problem. How does the worker thread deal with the situation? It likely cannot fix the issue as it is
usually oblivious of the purpose of the failed task. The "caller" thread needs to be notified somehow,
but there is no call-stack to unwind with an exception. Failure notification can only be done via a side-channel,
but there is no call stack to unwind with an exception. Failure notification can only be done via a side-channel,
for example putting an error code where the "caller" thread otherwise expects the result once ready.
If this notification is not in place, the "caller" never gets notified of a failure and the task is lost!
**This is surprisingly close to how networked systems work where messages/requests can get lost/fail without any
**This is surprisingly similar to how networked systems work where messages/requests can get lost/fail without any
notification.**
This bad situation gets worse when things go really wrong and a worker backed by a thread encounters a bug and ends
up in an unrecoverable situation. For example, maybe an internal exception caused by a bug bubbles up to the root of
the thread and the thread shuts down. This immediately raises the question, who should restart the normal operation
up in an unrecoverable situation. For example, an internal exception caused by a bug bubbles up to the root of
the thread and makes the thread shut down. This immediately raises the question, who should restart the normal operation
of the service hosted by the thread, and how should it be restored to a known-good state? At first glance,
this might seem manageable, but we are suddenly faced by a new, unexpected phenomena: the actual task,
that the thread was currently working on, is no longer in the shared memory location where tasks are taken from
(usually a queue). In fact, due to the exception reaching to the top, unwinding all the callstack,
(usually a queue). In fact, due to the exception reaching to the top, unwinding all of the call stack,
the task state is fully lost! **We have lost a message even though this is local communication with no networking
involved (where message losses are expected).**
involved (where message losses are to be expected).**
**In summary:**
* **To achieve any meaningful concurrency and performance on current systems threads must delegate tasks among each
* **To achieve any meaningful concurrency and performance on current systems, threads must delegate tasks among each
other in an efficient way without blocking. With this style of task-delegating concurrency
(and even more so with networked/distributed computing) callstack-based error handling breaks down and new,
explicit error signaling mechanisms need to be introduced. Failure becomes part of the domain model.**
(and even more so with networked/distributed computing) call stack-based error handling breaks down and new,
explicit error signaling mechanisms need to be introduced. Failures become part of the domain model.**
* **Concurrent systems with work delegation needs to handle service faults and have principled means to recover from them.
Clients of such services need to be aware that tasks/messages might get lost during restarts.
Even if loss does not happen, a response might be delayed arbitrarily due to previously enqueued tasks
@ -165,18 +162,18 @@ principled way, allowing systems to behave in a way that better matches our ment
In particular, we would like to:
* Enforce encapsulation without resorting to locks
* Enforce encapsulation without resorting to locks.
* Use the model of cooperative entities reacting to signals, changing state and sending signals to each other
to drive the whole application forward
* Stop worrying about the executing mechanism ("snaky-sneaky" threads) which is in a mismatch with our world view
to drive the whole application forward.
* Stop worrying about an executing mechanism which is a mismatch to our world view.
The actor model accomplishes all of these goals. The following topics describe how.
### Use of message passing avoids locking and blocking
### Usage of message passing avoids locking and blocking
Instead of calling methods, actors send messages to each other. Sending a message does not transfer the thread
of execution from the sender to the destination. An actor can send a message and continue without blocking.
It can do more work, send and receive messages.
It can, therefore, do more work, send and receive messages.
With objects, when a method returns, it releases control of its executing thread. In this respect, actors behave
much like objects, they react to messages and return execution when they finish processing the current message.
@ -184,10 +181,9 @@ In this way, actors actually achieve the execution we imagined for objects:
![actors interact with each other by sending messages](diagrams/actor_graph.png)
An important consequence of passing messages instead of calling methods is that messages have no return value.
An important difference of passing messages instead of calling methods is that messages have no return value.
By sending a message, an actor delegates work to another actor. As we saw in @ref:[The illusion of a call stack](actors-intro.md#the-illusion-of-a-call-stack),
if it expected a return value, the sending actor would either need to block (issue: locks and sacrifice of a thread),
or to execute the other actor's work on the same thread (issue: "snakes-in-a-maze").
if it expected a return value, the sending actor would either need to block or to execute the other actor's work on the same thread.
Instead, the receiving actor delivers the results in a reply message.
The second key change we need in our model is to reinstate encapsulation. Actors react to messages just like objects
@ -200,26 +196,26 @@ the invariants of an actor can be kept without synchronization. This happens aut
![messages don't invalidate invariants as they are processed sequentially](diagrams/serialized_timeline_invariants.png)
In summary, this is what happens when an actor receives a message. It:
In summary, this is what happens when an actor receives a message:
1. Adds the message to the end of a queue
2. If the actor was not scheduled for execution, it is marked as ready to execute
3. A (hidden) scheduler entity takes the actor and starts executing it
4. Actor picks the message from the front of the queue
5. Actor modifies internal state, sends messages to other actors
6. Actor is unscheduled
1. The actor adds the message to the end of a queue.
2. If the actor was not scheduled for execution, it is marked as ready to execute.
3. A (hidden) scheduler entity takes the actor and starts executing it.
4. Actor picks the message from the front of the queue.
5. Actor modifies internal state, sends messages to other actors.
6. The actor is unscheduled.
To accomplish this behavior, actors have
To accomplish this behavior, actors have:
* A Mailbox (the queue where messages end up)
* A Behavior (the state of the actor, internal variables etc.)
* Messages (pieces of data representing a signal, similar to method calls and their parameters)
* A Mailbox (the queue where messages end up).
* A Behavior (the state of the actor, internal variables etc.).
* Messages (pieces of data representing a signal, similar to method calls and their parameters).
* An Execution Environment (the machinery that takes actors that have messages to react to and invokes
their message handling code)
* An Address (more on this later)
their message handling code).
* An Address (more on this later).
Messages are put into Mailboxes of Actors, then the Behavior of the actor describes how the actor responds to
messages (like sending more messages and/or changing state). The Execution Environment orchestrates a pool of threads
Messages are put into so-called Mailboxes of Actors. The Behavior of the actor describes how the actor responds to
messages (like sending more messages and/or changing state). An Execution Environment orchestrates a pool of threads
to drive all these actions completely transparently.
This is a very simple model and it solves the issues enumerated previously:
@ -229,13 +225,9 @@ This is a very simple model and it solves the issues enumerated previously:
* There is no need for locks. Modifying the internal state of an actor is only possible via messages, which are
processed one at a time eliminating races when trying to keep invariants.
* There are no locks used anywhere, and senders are not blocked. Millions of actors can be efficiently scheduled on a
dozen of threads reaching the full potential of modern CPUs. Task delegation is the natural mode of operation
for actors.
dozen of threads reaching the full potential of modern CPUs. Task delegation is the natural mode of operation for actors.
* State of actors is local and not shared, changes and data is propagated via messages, which maps to how modern
memory hierarchy actually works. In many cases this means transferring over only the cache lines that contain the
data in the message while keeping local state and data cached at the original core. The same model maps exactly
to remote communication where state is kept in the RAM of machines and changes/data is propagated over the network
as packets.
memory hierarchy actually works. In many cases, this means transferring over only the cache lines that contain the data in the message while keeping local state and data cached at the original core. The same model maps exactly to remote communication where the state is kept in the RAM of machines and changes/data is propagated over the network as packets.
### Actors handle error situations gracefully
@ -245,21 +237,16 @@ error situations differently. There are two kinds of errors we need to consider:
* The first case is when the delegated task on the target actor failed due to an error in the task (typically some
validation issue, like a non-existent user ID). In this case, the service encapsulated by the target actor is intact,
it is only the task that itself is erroneous.
The service actor should reply to the sender with a message, presenting the error case. There is nothing special
here, errors are part of the domain and hence become ordinary messages.
The service actor should reply to the sender with a message, presenting the error case. There is nothing special here, errors are part of the domain and hence become ordinary messages.
* The second case is when a service itself encounters an internal fault. Akka enforces that all actors are organized
into a tree, an actor that creates another actor becomes the parent of that new actor. This is very similar
how operating systems organize processes into a tree. Just like with processes, when an actor fails,
into a tree-like hierarchy, i.e. an actor that creates another actor becomes the parent of that new actor. This is very similar how operating systems organize processes into a tree. Just like with processes, when an actor fails,
its parent actor is notified and it can react to the failure. Also, if the parent actor is stopped,
all of its children are recursively stopped, too. This service is called supervision and it is central to Akka.
![actors supervise and handle the failures of child actors](diagrams/actor_tree_supervision.png)
A supervisor (parent) can decide to restart its child actors on certain types of failures, or stop them completely on
A supervisor (parent) can decide to restart its child actors on certain types of failures or stop them completely on
others. Children never go silently dead (with the notable exception of entering an infinite loop) instead they are
either failing and their parent can react to the fault, or they are stopped (in which case interested parties are
automatically notified). There is always a responsible entity for managing an actor: its parent. Restarts are not
visible from the outside: collaborating actors can keep continuing sending messages while the target actor restarts.

View file

@ -6,9 +6,9 @@
* [What are Actors?](actors-intro.md)
* [Akka Libraries and Modules](modules.md)
* [Your First Akka Application - Hello World](quickstart.md)
* [Your second Akka application, part 1: Top-level architecture](tutorial_1.md)
* [Your second Akka application, part 2: The Device actor](tutorial_2.md)
* [Your second Akka application, part 3: Device groups](tutorial_3.md)
* [Your second Akka application, part 4: Querying a group of devices](tutorial_4.md)
* [Your Second Akka Application, Part 1: Top-level Architecture](tutorial_1.md)
* [Your Second Akka Application, Part 2: The Device Actor](tutorial_2.md)
* [Your Second Akka Application, Part 3: Device Groups](tutorial_3.md)
* [Your Second Akka Application, Part 4: Querying a Group of Devices](tutorial_4.md)
@@@

View file

@ -12,11 +12,11 @@ architectures.
To deal with these realities, Akka provides:
* Multi-threaded behavior without use of low-level concurrency constructs like
* Multi-threaded behavior without the use of low-level concurrency constructs like
atomics or locks. You do not even need to think about memory visibility issues.
* Transparent remote communication between systems and their components. You do
not need to write or maintain difficult networking code.
* A clustered, high-availability architecture that can scale-out (or in) on demand.
* A clustered, high-availability architecture that is elastic, scales in or out, on demand.
All of these features are available through a uniform programming model: Akka exploits the actor model
to provide a level of abstraction that makes it easier to write correct concurrent, parallel and distributed systems.
@ -28,7 +28,7 @@ By learning Akka and its actor model, you will gain access to a vast and deep se
distributed/parallel systems problems in a uniform programming model where everything fits together tightly and
efficiently.
## What is the actor model?
## What is the Actor Model?
The characteristics of today's computing environments are vastly different from the ones in use when the programming
models of yesterday were conceived. Actors were invented decades ago by @extref[Carl Hewitt](wikipedia:Carl_Hewitt#Actor_model).
@ -41,9 +41,9 @@ communicating with each other by explicit message passing.
As computational entities, actors have these characteristics:
* They communicate with asynchronous messaging instead of method calls
* They manage their own state
* When responding to a message, they can:
* They communicate with asynchronous messaging instead of method calls
* They manage their own state
* When responding to a message, they can:
* Create other (child) actors
* Send messages to other actors
* Stop (child) actors or themselves

View file

@ -4,41 +4,41 @@ Before we delve further into writing our first actors, we should stop for a mome
that come out-of-the-box. This will help you identify which modules and libraries provide the functionality you
want to use in your system.
### Actors (`akka-actor` library, the core)
### Actors (`akka-actor` Library, the Core)
The use of actors across Akka libraries provides a consistent, integrated model that relieves you from individually
solving the challenges that arise in concurrent or distributed system design. From a birds-eye view,
actors are a programming paradigm that takes encapsulation (one of the pillars of OOP) to its extreme.
actors are a programming paradigm that takes encapsulation, one of the pillars of OOP, to its extreme.
Unlike objects, actors encapsulate not only their
state but their execution. Communication with actors is not via method calls but by passing messages. While this
difference seems to be minor, this is actually what allows us to break clean from the limitations of OOP when it
difference may seem minor, it is actually what allows us to break clean from the limitations of OOP when it
comes to concurrency and remote communication. Dont worry if this description feels too high level to fully grasp
yet, in the next chapter we will explain actors in detail. For now, the important point is that this is a model that
handles concurrency and distribution at the fundamental level instead of ad hoc patched attempts to bring these
features to OOP.
Problems that actors solve include:
Challenges that actors solve include:
* How to build and design high-performance, concurrent applications?
* How to handle errors in a multi-threaded environment?
* How to protect my project from the pitfalls of concurrency?
* How to build and design high-performance, concurrent applications.
* How to handle errors in a multi-threaded environment.
* How to protect my project from the pitfalls of concurrency.
### Remoting
Remoting enables actors that are remote from each other, living on different computers, to seamlessly exchange messages.
Remoting enables actors that are remote, living on different computers, to seamlessly exchange messages.
While distributed as a JAR artifact, Remoting resembles a module more than it does a library. You enable it mostly
with configuration, it has only a few APIs. Thanks to the actor model, remote and local message sends look exactly the
same. The patterns that you use locally on multi-core systems translate directly to remote systems.
with configuration, it has only a few APIs. Thanks to the actor model, a remote and local message send looks exactly the
same. The patterns that you use on local systems translate directly to remote systems.
You will rarely need to use Remoting directly, but it provides the foundation on which the Cluster subsystem is built.
Some of the problems Remoting solves are
Some of the challenges Remoting solves are:
* How to address actor systems living on remote hosts?
* How to address individual actors on remote actor systems?
* How to turn messages to bytes on the wire?
* How to address actor systems living on remote hosts.
* How to address individual actors on remote actor systems.
* How to turn messages to bytes on the wire.
* How to manage low-level, network connections (and reconnections) between hosts, detect crashed actor systems and hosts,
all transparently?
* How to multiplex communications from unrelated set of actors on the same network connection, all transparently?
all transparently.
* How to multiplex communications from an unrelated set of actors on the same network connection, all transparently.
### Cluster
@ -46,16 +46,16 @@ If you have a set of actor systems that cooperate to solve some business problem
systems in a disciplined way. While Remoting solves the problem of addressing and communicating with components of
remote systems, Clustering gives you the ability to organize these into a "meta-system" tied together by a membership
protocol. **In most cases, you want to use the Cluster module instead of using Remoting directly.**
Cluster provides an additional set of services on top of Remoting that most real world applications need.
Clustering provides an additional set of services on top of Remoting that most real world applications need.
The problems the Cluster module solves (among others) are
The challenges the Cluster module solves, among others, are:
* How to maintain a set of actor systems (a cluster) that can communicate with each other and consider each other as part of the cluster?
* How to introduce a new system safely to the set of already existing members?
* How to detect reliably systems that are temporarily unreachable?
* How to maintain a set of actor systems (a cluster) that can communicate with each other and consider each other as part of the cluster.
* How to introduce a new system safely to the set of already existing members.
* How to reliably detect systems that are temporarily unreachable.
* How to remove failed hosts/systems (or scale down the system) so that all remaining members agree on the remaining subset of the cluster?
* How to distribute computations among the current set of members?
* How do I designate members of the cluster to a certain role, in other words to provide certain services and not others?
* How to distribute computations among the current set of members.
* How do I designate members of the cluster to a certain role, in other words, to provide certain services and not others.
### Cluster Sharding
@ -63,12 +63,12 @@ Sharding helps to solve the problem of distributing a set of actors among member
Sharding is a pattern that mostly used together with Persistence to balance a large set of persistent entities
(backed by actors) to members of a cluster and also migrate them to other nodes when members crash or leave.
The problem space that Sharding targets:
The challenge space that Sharding targets:
* How do I model and scale out a large set of stateful entities on a set of systems?
* How do I ensure that entities in the cluster are distributed properly so that load is properly balanced across the machines?
* How do I ensure migrating entities from a crashed system without losing state?
* How do I ensure that an entity does not exist on multiple systems at the same time and hence kept consistent?
* How to model and scale out a large set of stateful entities on a set of systems.
* How to ensure that entities in the cluster are distributed properly so that load is properly balanced across the machines.
* How to ensure migrating entities from a crashed system without losing the state.
* How to ensure that an entity does not exist on multiple systems at the same time and hence kept consistent.
### Cluster Singleton
@ -79,83 +79,85 @@ there are scenarios where the use of this pattern is unavoidable. Cluster single
actor system which will host a particular actor while other systems can always access said service independently from
where it is.
The Singleton module can be used to solve these problems:
The Singleton module can be used to solve these challenges:
* How do I ensure that only one instance is running of a service in the whole cluster?
* How do I ensure that the service is up even if the system hosting it currently crashes or shut down during the process of scaling down?
* How do I reach this instance from any member of the cluster assuming that it can migrate to other systems over time?
* How to ensure that only one instance of a service is running in the whole cluster.
* How to ensure that the service is up even if the system hosting it currently crashes or shut down during the process of scaling down.
* How to reach this instance from any member of the cluster assuming that it can migrate to other systems over time.
### Cluster Publish-Subscribe
For coordination among systems it is often necessary to distribute messages to all, or one system of a set of
For coordination among systems, it is often necessary to distribute messages to all, or one system of a set of
interested systems in a cluster. This pattern is usually called publish-subscribe and this module solves this exact
problem. It is possible to broadcast messages to all subscribers of a topic, or send a message to an arbitrary actor that has expressed interest.
problem. It is possible to broadcast messages to all subscribers of a topic or send a message to an arbitrary actor that has expressed interest.
Cluster Publish-Subscribe is intended to solve the following problems:
Cluster Publish-Subscribe is intended to solve the following challenges:
* How do I broadcast messages to an interested set of parties in a cluster?
* How do I send a message to a member from an interested set of parties in a cluster?
* How to subscribe and unsubscribe for events of a certain topic in the cluster?
* How to broadcast messages to an interested set of parties in a cluster.
* How to send a message to a member from an interested set of parties in a cluster.
* How to subscribe and unsubscribe for events of a certain topic in the cluster.
### Persistence
Just like objects in OOP actors keep their state in volatile memory. Once the system is shut down, gracefully or
because of a crash, all data that was in memory is lost. Persistence provide patterns to enable actors to persist
events that lead to their current state. Upon startup events can be replayed to restore the state of the entity hosted
Just like objects in OOP, actors keep their state in volatile memory. Once the system is shut down, gracefully or
because of a crash, all data that was in memory is lost. Persistence provides patterns to enable actors to persist
events that lead to their current state. Upon startup, events can be replayed to restore the state of the entity hosted
by the actor. The event stream can be queried and fed into additional processing pipelines (an external Big Data
cluster for example) or alternate views (like reports).
Persistence tackles the following problems:
Persistence tackles the following challenges:
* How do I restore the state of an entity/actor when system restarts or crashes?
* How do I implement a [CQRS system](https://msdn.microsoft.com/en-us/library/jj591573.aspx)?
* How do I ensure reliable delivery of messages in face of network errors and system crashes?
* How do I introspect domain events that has lead an entity to its current state?
* How do I leverage [Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html) in my application to support long-running processes while the project continues to evolve.
* How to restore the state of an entity/actor when system restarts or crashes.
* How to implement a [CQRS system](https://msdn.microsoft.com/en-us/library/jj591573.aspx).
* How to ensure reliable delivery of messages in face of network errors and system crashes.
* How to introspect domain events that have lead an entity to its current state.
* How to leverage [Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html) in my application to support long-running processes while the project continues to evolve.
### Distributed Data
In situations where eventual consistency is acceptable it is possible to share data between nodes in
In situations where eventual consistency is acceptable, it is possible to share data between nodes in
an Akka Cluster and accept both reads and writes even in the face of cluster partitions. This can be
achieved using Conflict Free Replicated Data Types (CRDTs), where writes on different nodes can
happen concurrently and are merged in a predictable way afterwards. The Distributed Data module
achieved using [Conflict Free Replicated Data Types](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type) (CRDTs), where writes on different nodes can
happen concurrently and are merged in a predictable way afterward. The Distributed Data module
provides infrastructure to share data and a number of useful data types.
Distributed Data is intended to solve the following challenges:
* How can I accept writes even in the face of cluster partitions?
* How can I share data while at the same time ensuring low-latency local read and write access?
* How to accept writes even in the face of cluster partitions.
* How to share data while at the same time ensuring low-latency local read and write access.
### Streams
Actors are a fundamental model for concurrency, but there are common patterns where their use requires the user
to implement the same pattern over and over. Very common is the scenario where a chain (or graph) of actors need to
process a potentially large (or infinite) stream of sequential events and properly coordinate resource usage so that
faster processing stages dont overwhelm slower ones in the chain (or graph). Streams provide a higher-level
to implement the same pattern over and over. Very common is the scenario where a chain, or graph, of actors, need to
process a potentially large, or infinite, stream of sequential events and properly coordinate resource usage so that
faster processing stages does not overwhelm slower ones in the chain or graph. Streams provide a higher-level
abstraction on top of actors that simplifies writing such processing networks, handling all the fine details in the
background and providing a safe, typed, composable programming model. Streams is also an implementation
of the [Reactive Streams standard](http://www.reactive-streams.org) which enables integration with all 3rd
of the [Reactive Streams standard](http://www.reactive-streams.org) which enables integration with all third
party implementations of that standard.
* How do I handle streams of events or large datasets with high performance, exploiting concurrency and keep resource usage tight?
* How do I assemble reusable pieces of event/data processing into flexible pipelines?
* How do I connect asynchronous services in a flexible way to each other, and have good performance?
* How do I provide or consume Reactive Streams compliant interfaces to interface with a 3rd party library?
Streams solve the following challenges:
* How to handle streams of events or large datasets with high performance, exploiting concurrency and keep resource usage tight.
* How to assemble reusable pieces of event/data processing into flexible pipelines.
* How to connect asynchronous services in a flexible way to each other, and have good performance.
* How to provide or consume Reactive Streams compliant interfaces to interface with a third party library.
### HTTP
The de facto standard for providing APIs remotely (internal or external) is HTTP. Akka provides a library to provide or
consume such HTTP services by giving a set of tools to create HTTP services (and serve them) and a client that can be
used to consume other services. These tools are particularly suited to streaming in and out large set of data or real
time events by leveraging the underlying model of Akka Streams.
The de facto standard for providing APIs remotely, internal or external, is [HTTP](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol). Akka provides a library to construct or consume such HTTP services by giving a set of tools to create HTTP services (and serve them) and a client that can be
used to consume other services. These tools are particularly suited to streaming in and out a large set of data or real-time events by leveraging the underlying model of Akka Streams.
* How do I expose services of my system or cluster of systems to the external world via an HTTP API in a performant way?
* How do I stream large datasets in and out of a system using HTTP?
* How do I stream live events in and out of a system using HTTP?
Some of the challenges that HTTP tackles:
* How to expose services of a system or cluster to the external world via an HTTP API in a performant way.
* How to stream large datasets in and out of a system using HTTP.
* How to stream live events in and out of a system using HTTP.
***
This is an incomplete list of all the available modules, but it gives a nice overview of the landscape of modules
The above is an incomplete list of all the available modules, but it gives a nice overview of the landscape of modules
and the level of sophistication you can reach when you start building systems on top of Akka. All these modules
integrate with together seamlessly. For example, take a large set of stateful business objects
(a document, a shopping cart, etc) that is accessed by on-line users of your website. Model these as sharded
@ -163,7 +165,7 @@ entities using Sharding and Persistence to keep them balanced across a cluster t
(for example during an advertising campaign before holidays) and keep them available even if some systems crash.
Take the real-time stream of domain events of your business objects with Persistence Query and use Streams to pipe
it into a streaming BigData engine. Take the output of that engine as a Stream, manipulate it using Akka Streams
operators and expose it as websocket connections served by a load balanced set of HTTP servers hosted by your cluster
operators and expose it as web socket connections served by a load balanced set of HTTP servers hosted by your cluster
to power your real-time business analytics tool.
Got you interested?
Hope this have gotten you interested? Keep on reading to learn more.

View file

@ -1,46 +1,46 @@
# Your First Akka Application - Hello World
After all this introduction, we are ready to build our first actor system. We will do this in three chapters.
In this first chapter we will help you to set up your project and tools and have a simple "Hello World" demo running.
We will keep this to the bare minimum and then extend the sample application in the next chapter. Finally we review
After all this introduction, we are ready to build our first actor system. We will do so in three chapters.
This first chapter will help you to set up your project, tools and have a simple "Hello World" demo running.
We will keep this section to a bare minimum and then extend the sample application in the next chapter. Finally, we review
what we have learned in the third chapter, looking in detail how the pieces work and fit together.
> Our goal in this chapter is to set up a working environment for you, create an application that starts up and stops
an ActorSystem and create an actor which we will test.
As the very first thing, we need to make sure that we can compile our project and have a working IDE setup to be
able to edit code comfortably. Depending on your preference for build tool and IDE there are multiple paths you can
follow here.
able to edit code comfortably. Depending on preference for build tool and IDE there are multiple paths that can
be followed.
## Setting up the build
## Setting Up the Build
Depending on your build tool, you need to set up the layout for your project and tell the build tool about your
dependencies (libraries that you want to use). There are common things to care for independently of your choice
Depending on the choice of build tool, we need to set up the layout for our project and tell the build tool about our
dependencies (libraries that we want to use). There are common things to care for independently of our choice
of build tool:
* Declare `akka-actor` as a dependency. This is the core library of Akka, the one we are now learning
* Declare `akka-testkit` as a dependency. This is a toolkit for testing Akka applications. Without this
dependency you will have a hard time testing actors.
* **Use the latest Akka version for new projects (unless you have additional constraints)!**
dependency we will have a hard time testing actors.
* **Use the latest Akka version for new projects (unless there are additional constraints)!**
* **Dont mix Akka versions! You are free to use any Akka version in your project, but you must use
that version for all Akka core projects** In this sample it means that `akka-actor` and `akka-testkit` should
always be of the same version.
always be the same version.
### sbt
If you plan to use sbt, your first step is to set up the directory structure for the project. sbt follows the
directory layout standard of Maven. Usually, you want to start with the following directories and files:
If the choice is to use [sbt](http://www.scala-sbt.org/), the first step is to set up the directory structure for the project. sbt follows the
directory layout standard of Maven. Usually, this means to start with the following directories and files:
* `/src`
* `/main` (this is where main, production classes live)
* `/scala` (this is where Scala classes live)
* `/java` (this is where Java classes live if you plan to use Java as well)
* `/java` (this is where Java classes live if Java is to be used)
* `/resources` (this is where non-source files live which are required on the classpath.
Typical example is application.conf which contains the configuration for your application.
A typical example is application.conf which contains the configuration for the application.
We will cover this in detail in CONFIG-SECTION)
* `/test`
* `/scala` (this is where Scala test and test helper classes live)
* `/java` (this is where Java test and test helper classes live if you plan to use Java as well)
* `/java` (this is where Java test and test helper classes live if Java is to be used)
* `/resources` (this is where non-source files live which are required on the classpath to run tests.
Typically this contains an application.conf that overrides the default configuration for tests. We will
cover this in detail in CONFIG-SECTION)
@ -48,12 +48,13 @@ directory layout standard of Maven. Usually, you want to start with the followin
* `build.properties` ()
* `build.sbt`
For example, if you have a Scala class `TestClass` in package `com.example.foo` then should go in
For example, if there is a Scala class `TestClass` in package `com.example.foo` then it should go in
`/src/main/scala/com/example/foo/TestClass.scala`.
The file `build.sbt` contains the necessary information for sbt about your project metadata and dependencies.
The file `build.sbt` contains the necessary information for sbt about the project metadata and dependencies.
For our sample project, this file should contain the following:
@@@vars
```scala
// build.sbt
@ -61,16 +62,17 @@ name := "intro-akka"
organization := "intro-akka.organization"
version := "0.1-SNAPSHOT"
scalaVersion := "2.11.8"
val AkkaVersion = "2.4.12"
scalaVersion := $scala.version$
val AkkaVersion = $akka.version$
libraryDependencies += "com.typesafe.akka" %% "akka-actor" % AkkaVersion
libraryDependencies += "com.typesafe.akka" %% "akka-testkit" % AkkaVersion % "test"
```
@@@
This simple file sets up first the project metadata (_name_ and _organization_; we just picked a sample one here).
Then we set up the version of the scala compiler we use, then set a variable with the Akka version we intend to
use (always try to use the latest).
This simple file sets up first the project metadata ( _name_ and _organization_; we just picked a sample one here).
Thereafter we set up the version of the Scala compiler we use, then set a variable with the Akka version we intend to
use. Always strive to use the latest version.
As the last step, we add two dependencies, `akka-actor` and `akka-testkit`. Note that we used the `AkkaVersion`
variable for both dependencies, ensuring that versions are not accidentally mixed and kept in sync when upgrading.
@ -78,36 +80,45 @@ variable for both dependencies, ensuring that versions are not accidentally mixe
Finally, check that everything works by running `sbt update` from the base directory of your project
(where the `build.sbt` file is).
## Setting up your IDE
## Setting up Your IDE
If you go with IDEA, you usually have flexible means to import project either manually created by one of the
previous steps from Setting up Your Build, or to let it create it for you. Depending on your build tool,
there are detailed steps in the IDEA documentation
If [IDEA](https://www.jetbrains.com/idea/) is the choice of IDE, it has flexible means to import project either manually created by one of the
previous steps from @ref:[setting up the build](#setting-up-the-build), or to let IDEA create it for you. Depending on your build tool,
there are detailed steps in the IDEA documentation:
sbt
: [https://www.jetbrains.com/help/idea/2016.2/getting-started-with-sbt.html](https://www.jetbrains.com/help/idea/2016.2/getting-started-with-sbt.html)
## Building the first application
## Building the First Application
Akka applications are simply Scala/Java applications, you dont need to set up any container (application server, etc.)
to have an actor system running. All we need is a class with a proper `main` method that stops and starts an actor
Akka applications are simply Scala or Java applications. To get an actor system up and running there is no need to set up any container, application server, etc. Instead, all that is needed is a class with a proper `main` method that starts and stops an actor
system. The pieces of the puzzle that we need to put together are: What is an actor system and why do I need one?
How can I start and stop it? Why do I need to stop it?
How can we start and stop it? Why do we need to stop it?
In Akka, actors belong to actor systems, which are instances of the type `ActorSystem`. This class acts as a
resource container which holds among others
resource container which holds among others:
* configuration shared by all actors in that system
* a pool of threads that will execute actors that are ready to process messages
* a dispatcher mechanism that dynamically assigns actors to threads of the pool
* a scheduler used for timer related tasks
* Configuration shared by all actors in that system.
* A pool of threads that will execute actors that are ready to process messages.
* A dispatcher mechanism that dynamically assigns actors to threads of the pool.
* A scheduler used for timer-related tasks.
The `ActorSystem` manages its actors and runs them in the background on its encapsulated thread pool.
This means that is must be explicitly shut down, otherwise these threads keep running and the JVM does
This means that is must be explicitly shut down, otherwise, these threads keep running and the JVM does
not exit (by default threads created by ActorSystem are not daemon threads; see the JDK documentation on
more details on [daemon or non-daemon threads](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html)).
The usual pattern is to have your system set up to stop on external signal (i.e. user pressing ENTER in the console).
This is the usual pattern to have your system set up and stopped on external signal
(user pressing ENTER in the console):
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 }
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 }
Here is the full example:
@@snip [HelloWorldApp.scala](../../../test/scala/quickstart/HelloWorldApp.scala) { #full-example }
Now that you have seen the basics of an Akka application it is time to dive deeper.

View file

@ -1,49 +1,46 @@
# Your second Akka application, part 1: Top-level architecture
# The Second Akka Application, Part 1: Top-level Architecture
In this and the following chapters we will build a sample Akka application to introduce you to the language of
actors and how problems can be formulated with them. It is a common hurdle for beginners to translate their problem
into actors even though they understand what they do on the high-level. We will build here the core logic of a small
application and guide you through common patterns that help you kickstart your projects with Akka.
In this and the following chapters, we will build a sample Akka application to introduce you to the language of
actors and how solutions can be formulated with them. It is a common hurdle for beginners to translate their project
into actors even though they don't understand what they do on the high-level. We will build the core logic of a small
application and this will serve as a guide for common patterns that will help to kickstart Akka projects.
The application we aim to write will be a (simplified) IoT system where devices can report temperature data coming
from sensors installed at the homes of users. Users will be able to query the current state of these sensors. To keep
things simple, we will not actually expose the application via HTTP or (other external API), we will concentrate only to the
core logic. On the other hand, we will write tests for the pieces of the application to get you comfortable and
proficient with testing actors very early.
The application we aim to write will be a simplified IoT system where devices, installed at the home of users, can report temperature data from sensors. Users will be able to query the current state of these sensors. To keep
things simple, we will not actually expose the application via HTTP or any other external API, we will, instead, concentrate only on the
core logic. However, we will write tests for the pieces of the application to get comfortable and
proficient with testing actors early on.
## Our goals for the IoT system
## Our Goals for the IoT System
We will build a simple IoT application with the bare essentials to demonstrate designing an Akka based system. The
application will consist of two main components:
We will build a simple IoT application with the bare essentials to demonstrate designing an Akka-based system. The application will consist of two main components:
* **Device data collection:** This component has the responsibility to maintain a local representation of the
otherwise remote devices. The devices will be organized into device groups, grouping together sensors belonging
to a home.
otherwise remote devices. The devices will be organized into device groups, grouping together sensors belonging to a home.
* **User dashboards:** This component has the responsibility to periodically collect data from the devices for a
logged in user and present the results as a report.
For simplicity, we will only collect temperature data for the devices, but in a real application our local representations
for a remote device (which we will model as an actor) would have many more responsibilities. Among others: reading the
for a remote device, which we will model as an actor, would have many more responsibilities. Among others; reading the
configuration of the device, changing the configuration, checking if the devices are unresponsive, etc. We leave
these complexities for now as they can be easily added as an exercise.
We will also not address the means by which the remote devices communicate with the local representations (actors). Instead
we just build an actor based API which such a network protocol could use. We will use tests for our API everywhere though.
We will also not address the means by which the remote devices communicate with the local representations (actors). Instead,
we just build an actor based API that such a network protocol could use. We will use tests for our API everywhere though.
The architecture of the application will look like this:
![box diagram of the architecture](diagrams/arch_boxes_diagram.png)
## Top level architecture
## Top Level Architecture
When writing prose, the hardest part is usually to write the first couple of sentences. There is a similar feeling
when you try to build your Akka system: what should be the first actor? Where should it live, what should it do?
Fortunately, unlike with prose, there are established best practices that guide you through these initial steps.
when trying to build an Akka system: What should be the first actor? Where should it live? What should it do?
Fortunately, unlike with prose, there are established best practices that can guide us through these initial steps.
When one creates an actor in Akka it always belongs to a certain parent. This means that actors are always organized
into a tree. In general, creating an actor can only happen from inside another actor. This creator actor becomes the
_parent_ of the newly created _child_ actor. You might ask then, who is the parent of the _first_ actor you create?
As we have seen in the previous chapters, to create a top-level actor one must call `context.actorOf()`. This does
As we have seen in the previous chapters, to create a top-level actor one must call `system.actorOf()`. This does
not create a "freestanding" actor though, instead, it injects the corresponding actor as a child into an already
existing tree:
@ -53,28 +50,27 @@ As you see, creating actors from the "top" injects those actors under the path `
an actor named `myActor` will end up having the path `/user/myActor`. In fact, there are three already existing
actors in the system:
- `/` the so called _root guardian_. This is the parent of all actors in the system, and the last one to stop
- `/` the so-called _root guardian_. This is the parent of all actors in the system, and the last one to stop
when the system itself is terminated.
- `/user` the _guardian_. **This is the parent actor for all user created actors**. The name `user` should not confuse
you, it has nothing to do with the logged in user, nor user handling in general. This name really means _userspace_
as this is the place where actors that do not access Akka internals live, i.e. all the actors created by users
of the Akka library. Every actor you will create will have the constant path `/user/` prepended to it.
as this is the place where actors that do not access Akka internals live, i.e. all the actors created by users of the Akka library. Every actor you will create will have the constant path `/user/` prepended to it.
- `/system` the _system guardian_.
The names of these built-in actors contain _guardian_ because these are _supervising_ every actor living as a child
of them (i.e. under their path). We will explain supervision in more detail, all you need to know now that every
unhandled failure from actors bubble up to their parent that, in turn, can decide how to handle this failure. These
of them, i.e. under their path. We will explain supervision in more detail, all you need to know now is that every
unhandled failure from actors bubbles up to their parent that, in turn, can decide how to handle this failure. These
predefined actors are guardians in the sense that they are the final lines of defense, where all unhandled failures
from user (or system) actors end up.
from user, or system, actors end up.
> Does the root guardian (the root path `/`) have a parent? As it turns out, it has. This special entity is called
> the "Bubble-Walker". This special entity is invisible for the user and only has uses internally.
### Structure of an ActorRef and paths of actors
### Structure of an ActorRef and Paths of Actors
The easiest way to see this in action is to simply print `ActorRef` instances. In this small experiment, we print
the reference of the first actor we create and then we create a child of this actor, and print its reference. We have
already created actors with `system.actorOf()`, which creates an actor under `/user` directly. We call these kind
already created actors with `system.actorOf()`, which creates an actor under `/user` directly. We call this kind
of actors _top level_, even though in practice they are not on the top of the hierarchy, only on the top of the
_user defined_ hierarchy. Since in practice we usually concern ourselves about actors under `/user` this is still a
convenient terminology, and we will stick to it.
@ -87,13 +83,13 @@ signature as its top-level counterpart. This is how it looks like in practice:
We see that the following two lines are printed
```
Actor[akka://testSystem/user/first-actor#1053618476]
Actor[akka://testSystem/user/first-actor/second-actor#-1544706041]
First : Actor[akka://testSystem/user/first-actor#1053618476]
Second: Actor[akka://testSystem/user/first-actor/second-actor#-1544706041]
```
First, we notice that all of the paths start with `akka://testSystem/`. Since all actor references are valid URLs, there
is a protocol field needed, which is `akka://` in the case of actors. Then, just like on the World Wide Web, the system
is identified. In our case this is `testSystem`, but could be any other name (if remote communications between multiple
is identified. In our case, this is `testSystem`, but could be any other name (if remote communication between multiple
systems is enabled this name is the hostname of the system so other systems can find it on the network). Our two actors,
as we have discussed before, live under user, and form a hierarchy:
@ -105,20 +101,20 @@ as we have discussed before, live under user, and form a hierarchy:
The last part of the actor reference, like `#1053618476` is a unique identifier of the actor living under the path.
This is usually not something the user needs to be concerned with, and we leave the discussion of this field for later.
### Hierarchy and lifecycle of actors
### Hierarchy and Lifecycle of Actors
We have so far seen that actors are organized into a **strict hierarchy**. This hierarchy consists of a predefined
upper layer of three actors (the root guardian, the user guardian and the system guardian), then the user created
top-level actors (those directly living under `/user`) and the children of those. We understand now how the hierarchy
looks like, but there is the nagging question left: _Why do we need this hierarchy? What is it used for?_
upper layer of three actors (the root guardian, the user guardian, and the system guardian), thereafter the user created
top-level actors (those directly living under `/user`) and the children of those. We now understand what the hierarchy
looks like, but there are some nagging unanswered questions: _Why do we need this hierarchy? What is it used for?_
The first use of the hierarchy is to manage the lifecycle of actors. Actors pop into existence when created, then later,
when the user requests, they are stopped. Whenever an actor is stopped, all of its children are _recursively stopped_,
too. This is a very useful property and greatly simplifies cleaning up resources and avoiding resource leaks (like open
at user requests, they are stopped. Whenever an actor is stopped, all of its children are _recursively stopped_ too.
This is a very useful property and greatly simplifies cleaning up resources and avoiding resource leaks (like open
sockets files, etc.). In fact, one of the overlooked difficulties when dealing with low-level multi-threaded code is
the management of the lifecycle of various concurrent resources.
the lifecycle management of various concurrent resources.
Stopping an actor can be done by the call `context.stop(actorRef)`. **It is considered a bad practice to stop arbitrary
Stopping an actor can be done by calling `context.stop(actorRef)`. **It is considered a bad practice to stop arbitrary
actors this way**. The recommended pattern is to call `context.stop(self)` inside an actor to stop itself, usually as
a response to some user defined stop message or when the actor is done with its job.
@ -141,20 +137,19 @@ second stopped
first stopped
```
We see that once we stopped the first actor, it recursively stopped our second actor, too, then it finished itself.
This ordering is strict, all `postStop()` hooks of the children are called before the `postStop()` hook of the parent
We see that when we stopped actor `first` it recursively stopped actor `second` and thereafter it stopped itself.
This ordering is strict, _all_ `postStop()` hooks of the children are called before the `postStop()` hook of the parent
is called.
The family of these lifecycle hooks is rich, and we recommend reading the actor lifecycle (TODO: reference-doc) section
of the reference for all the details.
The family of these lifecycle hooks is rich, and we recommend reading [the actor lifecycle](http://doc.akka.io/docs/akka/current/scala/actors.html#Actor_Lifecycle) section of the reference for all details.
### Hierarchy and failure handling (Supervision)
### Hierarchy and Failure Handling (Supervision)
Parents and children are not only connected by their lifecycles. Whenever an actor fails (throws an exception or
an unhandled exception bubbles out from `receive`) it is temporarily suspended. The failure information is propagated
then to the parent, which can decide what to do with the child actor now. The default _supervisor strategy_ is to
to the parent, which decides how to handle the exception caused by the child actor. The default _supervisor strategy_ is to
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 for now in this simple experiment:
the default strategy in this simple experiment:
@@snip [Hello.scala](../../../test/scala/tutorial_1/ActorHierarchyExperiments.scala) { #supervise }
@ -181,41 +176,40 @@ java.lang.Exception: I failed!
at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
```
We see that after failure the actor is stopped and immediately started again. We also see a log entry reporting the
exception that was handled, in this case our test exception. We only used here `preStart()` and `postStop()` hooks
We see that after failure the actor is stopped and immediately started. We also see a log entry reporting the
exception that was handled, in this case, our test exception. In this example we use `preStart()` and `postStop()` hooks
which are the default to be called after and before restarts, so we cannot distinguish from inside the actor if it
was started for the first time or restarted. This is usually the right thing to do, the purpose of the restart is to
set the actor in a known-good state, which usually means a clean starting stage. What actually happens though is
that **the preRestart()` and `postRestart()` methods are called which, if not overridden, by default delegate to
set the actor in a known-good state, which usually means a clean starting stage. **What actually happens though is
that the `preRestart()` and `postRestart()` methods are called which, if not overridden, by default delegate to
`postStop()` and `preStart()` respectively**. You can experiment with overriding these additional methods and see
how the output changes.
For the impatient, we also recommend looking into the supervision reference page (TODO: reference) for more in-depth
For the impatient, we also recommend looking into the [supervision reference page](http://doc.akka.io/docs/akka/current/general/supervision.html) for more in-depth
details.
### The first actor
### The First Actor
Actors are organized into a strict tree, where the lifecycle of every child is tied to the parent and where parents
are responsible to decide on the fate of failed children. At first, it might not be evident how to map our problem
are responsible for deciding the fate of failed children. At first, it might not be evident how to map our problem
to such a tree, but in practice, this is easier than it looks. All we need to do is to rewrite our architecture diagram
that contained nested boxes into a tree:
![actor tree diagram of the architecture](diagrams/arch_tree_diagram.png)
In simple terms, every component manages the lifecycle of the subcomponents. No subcomponent can outlive the parent
component. This is exactly how the actor hierarchy works. Also, it is desirable that a component handles the failure
of its subcomponents unless the failure leads to the failure of the whole component. Together, these two desirable
properties lead to the conclusion that the "contained-in" relationship of components should be mapped to the
component. This is exactly how the actor hierarchy works. Furthermore, it is desirable that a component handles the failure
of its subcomponents. Together, these two desirable properties lead to the conclusion that the "contained-in" relationship of components should be mapped to the
"children-of" relationship of actors.
The only question left is how to map the top-level components to actors. It might be tempting to create the actors
The remaining question is how to map the top-level components to actors. It might be tempting to create the actors
representing the main components as top-level actors. We instead, recommend creating an explicit component that
represent the whole application. In other words, we will have a single top-level actor in our actor system and have
represents the whole application. In other words, we will have a single top-level actor in our actor system and have
the main components as children of this actor.
The first actor happens to be rather simple now, as we have not implemented any of the components yet. What is new
is that we have dropped using `println()` and use instead the `ActorLogging` helper trait which allow us to use the
logging facility built into Akka directly:
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:
@@snip [Hello.scala](../../../test/scala/tutorial_1/IotSupervisor.scala) { #iot-supervisor }

View file

@ -1,50 +1,50 @@
# Your second Akka application, part 2: The Device actor
# Your Second Akka Application, Part 2: The Device Actor
In part 1 we explained how to view actor systems _in the large_, i.e. how components should be represented, how
actor should be arranged in the hierarchy. In this part we will look at actors _in the small_ by implementing an
actors should be arranged in the hierarchy. In this part, we will look at actors _in the small_ by implementing an
actor with the most common conversational patterns.
In particular, leaving the components aside for a while, we will implement the actor that represents a device. The
In particular, leaving the components aside for a while, we will implement an actor that represents a device. The
tasks of this actor will be rather simple:
* Collect temperature measurements
* Report the last measured temperature if asked
When working with objects we usually design our API as _interfaces_, which are basically a collection of abstract
methods to be filled in by the actual implementation. In the world of actors, the counterpart of interfaces are
methods to be filled out by the actual implementation. In the world of actors, the counterpart of interfaces is
protocols. While it is not possible to formalize general protocols in the programming language, we can formalize
its most basic elements: the messages.
## The query protocol
## The Query Protocol
Just because a device have been started it does not mean that it has immediately a temperature measurement. Hence, we
need to account for the case that a temperature is not present in our protocol. This fortunately means that we
need to account for the case where a temperature is not present in our protocol. This, fortunately, means that we
can test the query part of the actor without the write part present, as it can simply report an empty result.
The protocol for obtaining the current temperature from the device actor is rather simple looking:
The protocol for obtaining the current temperature from the device actor is rather simple:
1. Wait for a request for current temperature
2. respond to the request with a reply containing the current temperature or an indication that it is not yet
available
1. Wait for a request for the current temperature.
2. Respond to the request with a reply containing the current temperature or an indication that it is not yet
available.
We need two messages, one for the requests, and one for the replies. 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 }
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.
## Message ordering, delivery guarantees
## Message Ordering, Delivery Guarantees
In order to give some context to the discussion below, consider an application which spans multiple network hosts.
The basic mechanism for communication is the same whether sending to an actor on the local JVM or to a remote actor,
but of course there will be observable differences in the latency of delivery (possibly also depending on the bandwidth
of the network link and the message size) and the reliability. In case of a remote message send there are obviously
more steps involved which means that more can go wrong. Another aspect is that local sending will just pass a
but of course, there will be observable differences in the latency of delivery (possibly also depending on the bandwidth
of the network link and the message size) and the reliability. In the case of a remote message send there are
more steps involved which means that more can go wrong. Another aspect is that a local send will just pass a
reference to the message inside the same JVM, without any restrictions on the underlying object which is sent,
whereas a remote transport will place a limit on the message size.
It is also important to keep in mind, that while passing inside the same JVM is significantly more reliable, if an
It is also important to keep in mind, that while sending inside the same JVM is significantly more reliable, if an
actor fails due to a programmer error while processing the message, the effect is basically the same as if a remote,
network request fails due to the remote host crashing while processing the message. Even though in both cases the
service is recovered after a while (the actor is restarted by its supervisor, the host is restarted by an operator
@ -53,26 +53,25 @@ message could possibly be lost is the safe, pessimistic bet.**
These are the rules in Akka for message sends:
* at-most-once delivery, i.e. no guaranteed delivery
* message ordering is maintained per sender, receiver pair
* At-most-once delivery, i.e. no guaranteed delivery.
* Message ordering is maintained per sender, receiver pair.
### What does "at-most-once" mean?
### What Does "at-most-once" Mean?
When it comes to describing the semantics of a delivery mechanism, there are three basic categories:
* **at-most-once delivery** means that for each message handed to the mechanism, that message is delivered zero or
one times; in more casual terms it means that messages may be lost, but never duplicated.
* **at-least-once delivery** means that for each message handed to the mechanism potentially multiple attempts are made
at delivering it, such that at least one succeeds; again, in more casual terms this means that messages may be
duplicated but not lost.
* **exactly-once delivery** means that for each message handed to the mechanism exactly one delivery is made to
* **At-most-once delivery** means that for each message handed to the mechanism, that message is delivered zero or
one time; in more casual terms it means that messages may be lost, but never duplicated.
* **At-least-once delivery** means that for each message handed to the mechanism potentially multiple attempts are made
at delivering it, such that at least one succeeds; again, in more casual terms this means that messages may be duplicated but not lost.
* **Exactly-once delivery** means that for each message handed to the mechanism exactly one delivery is made to
the recipient; the message can neither be lost nor duplicated.
The first one is the cheapest, highest performance, least implementation overhead because it can be done in a
fire-and-forget fashion without keeping state at the sending end or in the transport mechanism.
The second one requires retries to counter transport losses, which means keeping state at the sending end and
having an acknowledgement mechanism at the receiving end. The third is most expensive, and has consequently worst
performance: in addition to the second it requires state to be kept at the receiving end in order to filter out
fire-and-forget fashion without keeping the state at the sending end or in the transport mechanism.
The second one requires retries to counter transport losses, which means keeping the state at the sending end and
having an acknowledgment mechanism at the receiving end. The third is most expensive, and has consequently worst
performance: in addition to the second, it requires the state to be kept at the receiving end in order to filter out
duplicate deliveries.
### Why No Guaranteed Delivery?
@ -80,11 +79,11 @@ duplicate deliveries.
At the core of the problem lies the question what exactly this guarantee shall mean, i.e. at which point does
the delivery considered to be guaranteed:
1. The message is sent out on the network?
2. The message is received by the other host?
3. The message is put into the target actor's mailbox?
4. The message is starting to be processed by the target actor?
5. The message is processed successfully by the target actor?
1. When the message is sent out on the network?
2. When the message is received by the other host?
3. When the message is put into the target actor's mailbox?
4. When the message is starting to be processed by the target actor?
5. When the message is processed successfully by the target actor?
Most frameworks/protocols claiming guaranteed delivery actually provide something similar to point 4 and 5. While this
sounds fair, **is this actually useful?** To understand the implications, consider a simple, practical example:
@ -93,13 +92,13 @@ disk in the database containing orders.
If we rely on the guarantees of such system it will report success as soon as the order has been submitted to the
internal API that has the responsibility to validate it, process it and put it into the database. Unfortunately,
immediately after the API has been invoked
immediately after the API has been invoked the following may happen:
* the host can immediately crash
* deserialization can fail
* validation can fail
* the database might be unavailable
* a programming error might occur
* The host can immediately crash.
* Deserialization can fail.
* Validation can fail.
* The database might be unavailable.
* A programming error might occur.
The problem is that the **guarantee of delivery** does not translate to the **domain level guarantee**. We only want to
report success once the order has been actually fully processed and persisted. **The only entity that can report
@ -110,29 +109,29 @@ that the order is now safely stored. **For these reasons Akka lifts the responsi
itself, i.e. you have to implement them yourself. On the other hand, you are in full control of the guarantees that you want
to provide**.
### Message ordering
### Message Ordering
The rule is that for a given pair of actors, messages sent directly from the first to the second will not be
received out-of-order. The word directly emphasizes that this guarantee only applies when sending with the tell
operator directly to the final destination, but not when employing mediators.
If
If:
* Actor `A1` sends messages `M1`, `M2`, `M3` to `A2`
* Actor `A3` sends messages `M4`, `M5`, `M6` to `A2`
* Actor `A1` sends messages `M1`, `M2`, `M3` to `A2`.
* Actor `A3` sends messages `M4`, `M5`, `M6` to `A2`.
This means that:
* If `M1` is delivered it must be delivered before `M2` and `M3`
* If `M2` is delivered it must be delivered before `M3`
* If `M4` is delivered it must be delivered before `M5` and `M6`
* If `M5` is delivered it must be delivered before `M6`
* `A2` can see messages from `A1` interleaved with messages from `A3`
* Since there is no guaranteed delivery, any of the messages may be dropped, i.e. not arrive at `A2`
* If `M1` is delivered it must be delivered before `M2` and `M3`.
* If `M2` is delivered it must be delivered before `M3`.
* If `M4` is delivered it must be delivered before `M5` and `M6`.
* If `M5` is delivered it must be delivered before `M6`.
* `A2` can see messages from `A1` interleaved with messages from `A3`.
* Since there is no guaranteed delivery, any of the messages may be dropped, i.e. not arrive at `A2`.
For the full details on delivery guarantees please refer to the reference page (TODO reference).
For the full details on delivery guarantees please refer to the [reference page](http://doc.akka.io/docs/akka/current/general/message-delivery-reliability.html).
### Revisiting the query protocol
### Revisiting the Query Protocol
There is nothing wrong with our first query protocol but it limits our flexibility. If we want to implement resends
in the actor that queries our device actor (because of timed out requests) or want to query multiple actors it
@ -147,16 +146,15 @@ our device actor:
@@snip [Hello.scala](../../../test/scala/tutorial_2/DeviceInProgress.scala) { #device-with-read }
We maintain the current temperature (which can be empty initially), 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.
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):
@@snip [Hello.scala](../../../test/scala/tutorial_2/DeviceSpec.scala) { #device-read-test }
## The write protocol
## The Write Protocol
As a first attempt, we could model recording the current temperature in the device actor as a single message:
@ -168,11 +166,11 @@ Such a message could possibly look like this:
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
application to provide success notifications. In our case we would like to send an acknowledgement to the sender
once we have updated our last temperature recording. Just like in the case of temperature queries and responses, it
is a good idea to include an ID field in this case, too, to provide maximum flexibility.
application to provide success notifications. In our case, we would like to send an acknowledgment to the sender
once we have updated our last temperature recording, e.g. `final case class TemperatureRecorded(requestId: Long)`.
Just like in the case of temperature queries and responses, it is a good idea to include an ID field to provide maximum flexibility.
Putting these 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 }
@ -181,7 +179,7 @@ together:
@@snip [Hello.scala](../../../test/scala/tutorial_2/DeviceSpec.scala) { #device-write-read-test }
## What is next?
## What is Next?
So far, we have started designing our overall architecture, and we wrote our first actor directly corresponding to the
domain. We now have to create the component that is responsible for maintaining groups of devices and the device

View file

@ -1,21 +1,21 @@
# Your second Akka application, part 3: Device groups
# Your Second Akka Application, Part 3: Device Groups and Manager
In this chapter we will integrate our device actors into a component that manages devices. When a new device comes
on-line, there is no actor representing it. We need to be able to ask the device manager component to create a new
In this chapter, we will integrate our device actors into a component that manages devices. When a new device comes
online, there is no actor representing it. We need to be able to ask the device manager component to create a new
device actor for us if necessary, in the required group (or return a reference to an already existing one).
Since we keep our tutorial system to the bare minimum, we have no actual component that interfaces with the external
world via some networking protocol. For our exercise we will just create the API necessary to integrate with such
world via some networking protocol. For our exercise, we will just create the API necessary to integrate with such
a component in the future. In a final system, the steps for connecting a device would look like this:
1. The device connects through some protocol to our system
2. The component managing network connections accepts the connection
3. The ID of the device and the ID of the group that it belongs is acquired
1. The device connects through some protocol to our system.
2. The component managing network connections accept the connection.
3. The ID of the device and the ID of the group that it belongs is acquired.
4. The device manager component is asked to create a group and device actor for the given IDs (or return an existing
one)
5. The device actor (just been created or located) responds with an acknowledgement, at the same time exposing its
ActorRef directly (by being the sender of the acknowledgement)
6. The networking component now uses the ActorRef of the device directly, avoiding going through the component
one).
5. The device actor (just been created or located) responds with an acknowledgment, at the same time exposing its
ActorRef directly (by being the sender of the acknowledgment).
6. The networking component now uses the ActorRef of the device directly, avoiding going through the component.
We are only concerned with steps 4 and 5 now. We will model the device manager component as an actor tree with three
levels:
@ -23,83 +23,82 @@ levels:
![device manager tree](diagrams/device_manager_tree.png)
* The top level is the supervisor actor representing the component. It is also the entry point to look up or create
group and device actors
* Group actors are supervisors of the devices belonging to the group. Groups both supervise the device actors and
also provide extra services, like querying the temperature readings from all the devices available
* Device actors manage all the interactions with the actual devices, storing temperature readings for example
group and device actors.
* Device group actors are supervisors of the devices belonging to the group. Apart from supervising the device actors they
also provide extra services, like querying the temperature readings from all the devices available.
* Device actors manage all the interactions with the actual devices, storing temperature readings for example.
When designing actor systems one of the main problems is to decide on the granularity of the actors. For example, it
would be perfectly possible to have only a single actor maintaining all the groups, and devices in `HashMap`s for
When designing actor systems one of the main challenges is to decide on the granularity of the actors. For example, it
would be perfectly possible to have only a single actor maintaining all the groups and devices in `HashMap`s for
example. It would be also reasonable to keep the groups as separate actors, but keep device state simply inside
the group actor.
We chose this three-level architecture for the following reasons:
We chose this three-layered architecture for the following reasons:
* Having groups as separate actors
* allows us to isolate failures happening in a group. If a programmer error would
happen in the single actor that keeps all state, it would be all wiped out once that actor is restarted affecting
groups that are otherwise non-faulty.
* simplifies the problem of querying all the devices belonging to a group (since it only contains state related
to the given group)
* increases the parallelism of the system by allowing to query multiple groups concurrently. Since groups have
* Having groups as individual actors:
* Allows us to isolate failures happening in a group. If a programmer error would
happen in the single actor that keeps all state, it would be all wiped out once that actor is restarted affecting groups that are otherwise non-faulty.
* Simplifies the problem of querying all the devices belonging to a group (since it only contains state related
to the given group).
* Increases the parallelism of the system by allowing to query multiple groups concurrently. Since groups have
dedicated actors, all of them can run concurrently.
* Having devices as separate actors
* allows us to isolate failures happening in a device actor from the rest of the devices
* increases the parallelism of collecting temperature readings as actual network connections from different devices
* Having devices as individual actors:
* Allows us to isolate failures happening in a device actor from the rest of the devices.
* Increases the parallelism of collecting temperature readings as actual network connections from different devices
can talk to the individual device actors directly, reducing contention points.
In practice, this system can be organized in different ways, all dependent on the characteristics of the interactions
In practice, a system can be organized in multiple ways, all depending on the characteristics of the interactions
between actors.
The following guidelines help to arrive at the right granularity
The following guidelines help to arrive at the right granularity:
* Prefer larger granularity to smaller. Introducing more fine-grained actors than needed causes more problems than
it solves
* Prefer finer granularity if it enables higher concurrency in the system
it solves.
* Prefer finer granularity if it enables higher concurrency in the system.
* Prefer finer granularity if actors need to handle complex conversations with other actors and hence have many
states. We will see a very good example for this in the next chapter.
* Prefer finer granularity if there is too many state to keep around in one place compared to dividing into smaller
* Prefer finer granularity if there is too much state to keep around in one place compared to dividing into smaller
actors.
* Prefer finer granularity if the current actor has multiple unrelated responsibilities that can fail and restored
individually
individually.
## The registration protocol
## The Registration Protocol
As the first step, we need to design the protocol for registering a device and getting an actor that will be responsible
for it. This protocol will be provided by the DeviceManager component itself, because that is the only actor that
is known upfront: groups and device actors are created on-demand. The steps of registering a device are the following:
As the first step, we need to design the protocol for registering a device and create an actor that will be responsible
for it. This protocol will be provided by the `DeviceManager` component itself because that is the only actor that
is known up front: device groups and device actors are created on-demand. The steps of registering a device are the following:
1. DeviceManager receives the request to register an actor for a given group and device ID
2. If the manager already has an actor for the group, it forwards the request to it. Otherwise it first creates
1. DeviceManager receives the request to track a device for a given group and device.
2. If the manager already has an actor for the device group, it forwards the request to it. Otherwise, it first creates
a new one and then forwards the request.
3. The DeviceGroup receives the request to register an actor for the given device ID
4. If the group already has an actor for the device ID, it forwards the request to it. Otherwise it first creates
3. The DeviceGroup receives the request to register an actor for the given device.
4. If the group already has an actor for the device, it forwards the request to it. Otherwise, it first creates
a new one and then forwards the request.
5. The device actor receives the request, and acknowledges it to the original sender. Since he is the sender of
the acknowledgement, the receiver will be able to learn its `ActorRef` and send direct messages to it in the future.
5. The device actor receives the request and acknowledges it to the original sender. Since the device actor is the sender of
the acknowledgment, the receiver, i.e. the device, will be able to learn its `ActorRef` and send direct messages to its device actor in the future.
Now that the steps are defined, we only need to define the messages that we will use to communicate requests and
their acknowledgement:
@@snip [Hello.scala](../../../test/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.
Nevertheless, it is a good exercise to add this ID.
## Add registration support to Device actor
## Add Registration Support to Device Actor
We start implementing the protocol from the bottom first. In practice both a top-down and bottom-up approach can
work, but in our case we benefit from the bottom-up approach as it allows us to immediately write tests for the
We start implementing the protocol from the bottom first. In practice, both a top-down and bottom-up approach can
work, but in our case, we benefit from the bottom-up approach as it allows us to immediately write tests for the
new features without mocking out parts.
At the bottom are the Device actors. Their job in this registration process is rather simple, just reply to the
registration request with an acknowledgement to the sender. *We will assume that the sender of the registration
At the bottom of our hierarchy are the `Device` actors. Their job in this registration process is rather simple, just reply to the
registration request with an acknowledgment to the sender. *We will assume that the sender of the registration
message is preserved in the upper layers.* We will show you in the next section how this can be achieved.
We also add a safeguard against requests that come with a mismatched group or device ID. This is how the resulting
code looks like:
the code looks like:
> NOTE: We used a feature of scala pattern matching where we can match if a certain field equals to an expected
value. This is achieved by variables included in backticks, like `` `variable` ``, and it means that the pattern
@ -111,21 +110,21 @@ We should not leave features untested, so we immediately write two new test case
registration, the other testing the case when IDs don't match:
> NOTE: We used the `expectNoMsg()` helper method from `TestProbe`. This assertion waits until the defined time-limit
and fails if it receives any messages during this period. If no messages are received during the wait period the
and fails if it receives any messages during this period. If no messages are received during the waiting period the
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.
@@snip [Hello.scala](../../../test/scala/tutorial_3/DeviceSpec.scala) { #device-registration-tests }
## Device group
## Device Group
We are done with the registration support at the device level, now we have to implement it at the group level. A group
has more work to do when it comes to registrations. It must either forward the request to an existing child, or it
should create one. To be able to look up child actors by their device IDs we will use a `Map`.
should create one. To be able to look up child actors by their device IDs we will use a `Map[String, ActorRef]`.
We also want to keep the original sender of the request so that our device actor can reply directly. This is possible
by using `forward` instead of the `!` operator. The only difference between the two is that `forward` keeps the original
sender while `!` always sets the sender to be the current actor. Just like with our device, 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:
@@snip [Hello.scala](../../../test/scala/tutorial_3/DeviceGroup.scala) { #device-group-register }
@ -136,47 +135,47 @@ to see if the actors are responding.
@@snip [Hello.scala](../../../test/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:
@@snip [Hello.scala](../../../test/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
we will need a way to remove those from the `Map`. We will assume that when a device is removed, its corresponding actor
is simply stopped. We need some way for the parent to be notified when one of the device actors are stopped. Unfortunately
supervision will not help, because it is used for error scenarios not graceful stopping.
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
is simply stopped. We need some way for the parent to be notified when one of the device actors are stopped. Unfortunately,
supervision will not help because it is used for error scenarios, not graceful stopping.
There is a feature in Akka that is exactly what we need here. It is possible for an actor to _watch_ another actor
and be notified if the other actor is stopped. This feature is called _Death Watch_ and it is an important tool for
any Akka application. Unlike supervision, watching is not limited to parent-child relationships, any actor can watch
any other actor given its `ActorRef`. After a watched actor stops, the watcher receives a `Terminated(ref)` message
which also contains the reference of the watched actor. The watcher can either handle this message explicitly, or, if
which also contains the reference to the watched actor. The watcher can either handle this message explicitly or, if
it does not handle it directly it will fail with a `DeathPactException`. This latter is useful if the actor cannot
longer perform its duties after its collaborator actor has been stopped. In our case, the group should still function
after one device have been stopped, so we need to handle this message. The steps we need to follow are the following:
1. Whenever we create a new device actor, we must also watch it
2. When we are notified that a device actor has been stopped we also need to remove it from the `Map` that maps
device IDs to children
1. Whenever we create a new device actor, we must also watch it.
2. When we are notified that a device actor has been stopped we also need to remove it from the `Map[String, ActorRef]` which maps
devices to device actors.
Unfortunately, the `Terminated` message contains only contains the `ActorRef` of the child actor but we does not know
its ID, which we need to remove it from the `Map` of existing ID-actor mappings. To be able to do this removal, we
need to introduce a second `Map` that allow us to find out the device ID corresponding to a given `ActorRef`. Putting
Unfortunately, the `Terminated` message contains only contains the `ActorRef` of the child actor but we do not know
its ID, which we need to remove it from the map of existing device to device actor mappings. To be able to do this removal, we
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:
@@snip [Hello.scala](../../../test/scala/tutorial_3/DeviceGroup.scala) { #device-group-remove }
Since so far we have no means to get from the group what are the devices it thinks are active, we cannot test our
new functionality yet. To make it testable, we add a new query capability that simply lists the currently active
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
device IDs:
@@snip [Hello.scala](../../../test/scala/tutorial_3/DeviceGroup.scala) { #device-group-full }
We have now almost everything to test the removal of devices. We only need now ways to:
We almost have everything to test the removal of devices. What is missing is:
* stop a device actor from our test case, from the outside: any actor can be stopped by simply sending a special
built-in message, `PoisonPill`, which instructs the actor to stop.
* be notified once the device actor is stopped: we can use the _Death Watch_ facility for this purpose, too. Thankfully
* Stopping a device actor from our test case, from the outside: any actor can be stopped by simply sending a special
the built-in message, `PoisonPill`, which instructs the actor to stop.
* Be notified once the device actor is stopped: we can use the _Death Watch_ facility for this purpose, too. Thankfully
the `TestProbe` has two messages that we can easily use, `watch()` to watch a specific actor, and `expectTerminated`
to assert that the watched actor has been terminated.
@ -186,25 +185,25 @@ a few devices. The second test case makes sure that the device ID is properly re
@@snip [Hello.scala](../../../test/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 group actor, with the only difference that it creates 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 }
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.
## What is next?
## What is Next?
We have now a hierarchic component for registering and tracking devices and recording measurements. We have seen
some conversation patterns like
We have now a hierarchical component for registering and tracking devices and recording measurements. We have seen
some conversation patterns like:
* request-respond (for temperature recordings)
* delegate-respond (for registration of devices)
* create-watch-terminate (for creating group and device actor as children)
* Request-respond (for temperature recordings).
* Delegate-respond (for registration of devices).
* Create-watch-terminate (for creating the group and device actor as children).
In the next chapter we will introduce group query capabilities, which will establish a new conversation pattern of
In the next chapter, we will introduce group query capabilities, which will establish a new conversation pattern of
scatter-gather. In particular, we will implement the functionality that allows users to query the status of all
the devices belonging to a group.

View file

@ -1,84 +1,84 @@
# Your second Akka application, part 4: Querying a group of devices
# Your Second Akka Application, Part 4: Querying a Group of Devices
The conversational patterns we have seen so far were simple in the sense that they required no or little state to be kept in the
actor that is only relevant to the conversation. Our device actors either simply returned a reading, which required no
state change, recorded a temperature, which was required an update of a single field, or in the most complex case,
managing groups and devices, we had to add or remove simple entries from a `Map`.
managing groups and devices, we had to add or remove simple entries from a map.
In this chapter we will see a more complex example. Our goal is to add a new service to the group actor, one which
allows querying the temperature from all running devices. We need to first investigate how we want our query API to
In this chapter, we will see a more complex example. Our goal is to add a new service to the group device actor, one which
allows querying the temperature from all running devices. Let us start by investigating how we want our query API to
behave.
The very first problem we face is that the set of devices is dynamic, and each device is represented by an actor that
The very first issue we face is that the set of devices is dynamic, and each device is represented by an actor that
can stop at any time. At the beginning of the query, we need to ask all of the device actors for the current temperature
that we know about. However, during the lifecycle of the query
that we know about. However, during the lifecycle of the query:
* a device actor may stop and not respond with a temperature reading
* a new device actor might start up, but we missed asking it for the current temperature
* A device actor may stop and not respond back with a temperature reading.
* A new device actor might start up, but we missed asking it for the current temperature.
There are many approaches that can be taken to address these issues, but the important point is to settle on what is
the desired behavior. We will pick the following two guarantees:
* when a query arrives to the group, the group actor takes a _snapshot_ of the existing device actors and will only
* When a query arrives at the group, the group actor takes a _snapshot_ of the existing device actors and will only
ask those for the temperature. Actors that are started _after_ the arrival of the query are simply ignored.
* when an actor stops during the query without answering (i.e. before all the actors we asked for the temperature
responded) we simply report back the fact to the sender of the query message
* When an actor stops during the query without answering (i.e. before all the actors we asked for the temperature
responded) we simply report back that fact to the sender of the query message.
Apart from device actors coming and going dynamically some actors might take a long time to answer, for example because
Apart from device actors coming and going dynamically, some actors might take a long time to answer, for example, because
they are stuck in an accidental infinite loop, or because they failed due to a bug and dropped our request. Ideally,
we would like to give a deadline to our query:
* the query is considered completed if either all actors have responded (or confirmed being stopped), or we reach
the deadline
* The query is considered completed if either all actors have responded (or confirmed being stopped), or we reach
the deadline.
Given these decisions, and the fact that a device might not have a temperature to record, we can define four states
that each device can be in, according to the query:
* It has a temperature available: `Temperature(value)`
* It has responded, but has no temperature available yet: `TemperatureNotAvailable`
* It has stopped before answering: `DeviceNotAvailable`
* It did not respond before the deadline: `DeviceTimedOut`
* It has a temperature available: `Temperature(value)`.
* It has responded, but has no temperature available yet: `TemperatureNotAvailable`.
* It has stopped before answering: `DeviceNotAvailable`.
* It did not respond before the deadline: `DeviceTimedOut`.
Summarizing these in message types we can add the following to `DeviceGroup`:
@@snip [Hello.scala](../../../test/scala/tutorial_4/DeviceGroup.scala) { #query-protocol }
## Implementing the query
## Implementing the Query
One of the approaches could be for implementing the query is to add more code to the group actor. While this is
One of the approaches for implementing the query could be to add more code to the group device actor. While this is
possible, in practice this can be very cumbersome and error prone. When we start a query, we need to take a snapshot
of the devices present at the start of the query and start a timer so that we can enforce the deadline. Unfortunately,
during the time we execute a query _another query_ might just arrive. For this other query of course we need to keep
track of the exact same information, but isolated from the previous query. This complicates the code and also poses
during the time we execute a query _another query_ might just arrive. For this other query, of course, we need to keep
track of the exact same information but isolated from the previous query. This complicates the code and also poses
some problems. For example, we would need a data structure that maps the `ActorRef`s of the devices to the queries
that use that device, so that they can be notified when such a device terminates, i.e. a `Terminated` message is
received.
There is a much simpler approach that is superior in every way, and it is the one we will implement. We will create
an actor that represents a _single query_ and which performs the tasks needed to complete the query in behalf of the
an actor that represents a _single query_ and which performs the tasks needed to complete the query on behalf of the
group actor. So far we have created actors that belonged to classical domain objects, but now, we will create an
actor that represents a process or task rather than an entity. This move keeps our group actor simple and gives
actor that represents a process or task rather than an entity. This move keeps our group device actor simple and gives
us better ways to test the query capability in isolation.
First, we need to design the lifecycle of our query actors. This consists of identifying its initial state, then
First, we need to design the lifecycle of our query actor. This consists of identifying its initial state, then
the first action to be taken by the actor, then, the cleanup if necessary. There are a few things the query should
need to be able to work:
* The snapshot of active device actors to query, and their IDs
* The requestID of the request that started the query (so we can include it in the reply)
* The snapshot of active device actors to query, and their IDs.
* The requestID of the request that started the query (so we can include it in the reply).
* The `ActorRef` of the actor who sent the group actor the query. We will send the reply to this actor directly.
* A timeout parameter, how long the query should wait for replies. Keeping this as a parameter will simplify testing.
Since we need to have a deadline until we are willing to wait for responses, we will need a new feature that we have
Since we need to have a timeout for how long we are willing to wait for responses, it is time to introduce a new feature that we have
not used yet: timers. Akka has a built-in scheduler facility for this exact purpose. Using it is simple, the
`scheduler.scheduleOnce(time, actorRef, message)` method will schedule the message `message` into the future by the
specified `time` and send it to the actor `actorRef`. To implement our query timeout we need to create the message
that represents that the deadline is due, we create a simple message `CollectionTimeout` without any parameters for
this purpose. The return value from `scheduleOnce` is a `Cancellable` which will be useful to cancel the timer
specified `time` and send it to the actor `actorRef`. To implement our query timeout we need to create a message
that represents the query timeout. We create a simple message `CollectionTimeout` without any parameters for
this purpose. The return value from `scheduleOnce` is a `Cancellable` which can be used to cancel the timer
if the query finishes successfully in time. Getting the scheduler is possible from the `ActorSystem`, which, in turn,
is accessible from the actor's context: `context.system.scheduler`. This needs an implicit `ExecutionContext` which
is basically the thread-pool that will execute the timer task itself. In our case we can just use the default dispatcher
which can be brought into scope by `import context.dispatcher`.
is basically the thread-pool that will execute the timer task itself. In our case, we use the same dispatcher
as the actor by importing `import context.dispatcher`.
At the start of the query, we need to ask each of the device actors for the current temperature. To be able to quickly
detect devices that stopped before they got the `ReadTemperature` message we will also watch each of the actors. This
@ -89,14 +89,14 @@ Putting together all these, the outline of our actor looks like this:
@@snip [Hello.scala](../../../test/scala/tutorial_4/DeviceGroupQuery.scala) { #query-outline }
The query (apart from the pending timer) has one stateful aspect about it: the actors that did not answer so far (or,
looking from the other way around, the set of actors that have replied or stopped). One way to track this state is
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
to create a mutable field in the actor (a `var`). There is another approach. It is also possible to change how
the actor responds to messages. By default, the `receive` block defines the behavior of the actor, but it is possible
to change it (even several times) during the life of the actor. This is possible by calling `context.become(newBehavior)`
to change it, several times, during the life of the actor. This is possible by calling `context.become(newBehavior)`
where `newBehavior` is anything with type `Receive` (which is just a shorthand for `PartialFunction[Any, Unit]`). A
`Receive` is just a function (or an object, if you like) it can be returned from a function. We will leverage this
to track the state of our actor.
`Receive` is just a function (or an object, if you like) that can be returned from another function. We will leverage this
feature to track the state of our actor.
As the first step, instead of defining `receive` directly, we delegate to another function to create the `Receive`, which
we will call `waitingForReplies`. This will keep track of two changing values, a `Map` of already received replies
@ -104,7 +104,7 @@ and a `Set` of actors that we still wait on. We have three events that we should
`RespondTemperature` message from one of the devices. Second, we can receive a `Terminated` message for a device actor
that has been stopped in the meantime. Finally, we can reach the deadline and receive a `CollectionTimeout`. In the
first two cases, we need to keep track of the replies, which we now simply delegate to a method `receivedResponse` which
we will discuss later. In the case of timeout, we need to simply take all the actors that has not yet replied yet
we will discuss later. In the case of timeout, we need to simply take all the actors that have not yet replied yet
(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:
@ -115,22 +115,22 @@ thing to note is that the function `waitingForReplies` **does not handle the mes
function that will handle the messages**. This means that if we call `waitingForReplies` again, with different parameters,
then it returns a brand new `Receive` that will use those new parameters. We have seen how we
can install the initial `Receive` by simply returning it from `receive`. In order to install a new one, to record a
new reply for example, we need some mechanism. This mechanism is the method `context.become(newReceive)` which will
new reply, for example, we need some mechanism. This mechanism is the method `context.become(newReceive)` which will
_change_ the actor's message handling function to the provided `newReceive` function. You can imagine that before
starting, your actor automatically calls `context.become(receive)`, i.e. installing the `Receive` function that
is returned from `receive`. This is another important observation: **it is not `receive` that handles the messages,
it just returns a `Receive` function that will actually handle the messages***.
it just returns a `Receive` function that will actually handle the messages**.
We now have to figure out what to do in `receivedResponse()`. First, we need to record the new result in the map
`repliesSoFar` and remove the actor from `stillWaiting`. Then, we need to check if there is any remaining actors
that we are waiting for. If there is none, we can send the result of the query to the original requester, and stop
We now have to figure out what to do in `receivedResponse`. First, we need to record the new result in the map
`repliesSoFar` and remove the actor from `stillWaiting`. The next step is to check if there are any remaining actors
we are waiting for. If there is none, we send the result of the query to the original requester and stop
the query actor. Otherwise, we need to update the `repliesSoFar` and `stillWaiting` structures and wait for more
messages.
In the code before, we treated `Terminated` as the implicit response `DeviceNotAvailable`, so `receivedResponse` does
not need to do anything special. There is one small task we still need to do. It is possible that we receive a proper
not need to do anything special. However, there is one small task we still need to do. It is possible that we receive a proper
response from a device actor, but then it stops during the lifetime of the query. We don't want this second event
overwrite the already received reply. In other words, we don't want to receive `Terminated` after we recorded the
to overwrite the already received reply. In other words, we don't want to receive `Terminated` after we recorded the
response. This is simple to achieve by calling `context.unwatch(ref)`. This method also ensures that we don't
receive `Terminated` events that are already in the mailbox of the actor. It is also safe to call this multiple times,
only the first call will have any effect, the rest is simply ignored.
@ -142,12 +142,12 @@ With all this knowledge, we can create the `receivedResponse` method:
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
simple example, not that much. The value of this style of state keeping becomes more evident when you suddenly have
_more kinds_ of states. For example imagine that the query have multiple phases that come each other. Since each phase
might have temporary data that is relevant only to that phase, keeping these as fields would pollute the global state
of the actor where it is not clear which field is used or ignored in which state. Using parameterized `Receive` "factory"
methods we can keep data that is only relevant to the state private to the state. It is still a good exercise to
rewrite the query using `var`s instead of `context.become()`. In general, it is a good practice to get comfortable
with the solution we have used here as it helps structuring more complex actor in a cleaner and more maintainable way.
_more kinds_ of states. Since each state
might have temporary data that is relevant itself, keeping these as fields would pollute the global state
of the actor, i.e. it is unclear what fields are used in what state. Using parameterized `Receive` "factory"
methods we can keep data private that is only relevant to the state. It is still a good exercise to
rewrite the query using `var`s instead of `context.become()`. However, it is recommended to get comfortable
with the solution we have used here as it helps structuring more complex actor code in a cleaner and more maintainable way.
Or query actor is now done:
@ -155,7 +155,7 @@ Or query actor is now done:
## Testing
It is time to test if the query actor is correct. There are various scenarios we need to test individually to make
Now let's verify the correctness of the query actor implementation. There are various scenarios we need to test individually to make
sure everything works as expected. To be able to do this, we need to simulate the device actors somehow to exercise
various normal or failure scenarios. Thankfully we took the list of collaborators (actually a `Map`) as a parameter
to the query actor, so we can easily pass in `TestProbe` references. In our first test, we try out the case when
@ -163,7 +163,7 @@ there are two devices and both report a temperature:
@@snip [Hello.scala](../../../test/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-normal }
That was the happy case, but we know that sometimes devices cannot provide a temperature measurement yet. 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:
@@snip [Hello.scala](../../../test/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-no-reading }
@ -173,7 +173,7 @@ We also know, that sometimes device actors stop before answering:
@@snip [Hello.scala](../../../test/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
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:
@@snip [Hello.scala](../../../test/scala/tutorial_4/DeviceGroupQuerySpec.scala) { #query-test-stopped-later }
@ -185,25 +185,21 @@ The final case is when not all devices respond in time. To keep our test relativ
Our query works as expected now, it is time to include this new functionality in the `DeviceGroup` actor now.
## Adding the query capability to the group
## Adding the Query Capability to the Group
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.
@@snip [Hello.scala](../../../test/scala/tutorial_4/DeviceGroup.scala) { #query-added }
It is probably worth to reiterate what we said in the beginning of the chapter. By keeping all the temporary state
that was only relevant to the query itself in a separate actor we kept the group actor very simple. It delegates
everything to child actors that needs temporary state not relevant to its main business. Also, multiple queries can
now run parallel to each other, as many as needed. In our case querying the individual device actors is fast, but
were this not the case, for example because the remote sensors need to be contacted over the network, this formulation
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
everything to child actors and therefore does not have to keep state that is not relevant to its core business. Also, multiple queries can
now run parallel to each other, in fact, as many as needed. In our case querying an individual device actor is a fast operation, but
if this were not the case, for example, because the remote sensors need to be contacted over the network, this design
would significantly improve throughput.
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:
@@snip [Hello.scala](../../../test/scala/tutorial_4/DeviceGroupSpec.scala) { #group-query-integration-test }

View file

@ -0,0 +1,32 @@
//#full-example
package quickstart
import akka.actor.{ Actor, ActorRef, Props, ActorSystem }
import scala.io.StdIn
object HelloWorldApp {
def main(args: Array[String]): Unit = {
//#create-send
val system = ActorSystem("hello-world-actor-system")
try {
// Create hello world actor
val helloWorldActor: ActorRef = system.actorOf(Props[HelloWorldActor], "HelloWorldActor")
// Send message to actor
helloWorldActor ! "World"
// Exit the system after ENTER is pressed
StdIn.readLine()
} finally {
system.terminate()
}
//#create-send
}
}
//#actor-impl
class HelloWorldActor extends Actor {
def receive = {
case msg: String => println(s"Hello $msg")
}
}
//#actor-impl
//#full-example

View file

@ -8,31 +8,31 @@ class PrintMyActorRefActor extends Actor {
override def receive: Receive = {
case "printit" =>
val secondRef = context.actorOf(Props.empty, "second-actor")
println(secondRef)
println(s"Second: $secondRef")
}
}
//#print-refs
//#start-stop
class StartStopActor1 extends Actor {
override def receive: Receive = {
case "stop" => context.stop(self)
}
override def preStart(): Unit = {
println("first started")
context.actorOf(Props[StartStopActor2], "second")
}
override def postStop(): Unit = println("first stopped")
override def receive: Receive = {
case "stop" => context.stop(self)
}
}
class StartStopActor2 extends Actor {
override def preStart(): Unit = println("second started")
override def postStop(): Unit = println("second stopped")
// Actor.emptyBehavior is a useful placeholder when we don't
// want to handle any messages in the actor.
override def receive: Receive = Actor.emptyBehavior
override def preStart(): Unit = println("second started")
override def postStop(): Unit = println("second stopped")
}
//#start-stop
@ -46,7 +46,6 @@ class SupervisingActor extends Actor {
}
class SupervisedActor extends Actor {
override def preStart(): Unit = println("supervised actor started")
override def postStop(): Unit = println("supervised actor stopped")
@ -59,29 +58,34 @@ class SupervisedActor extends Actor {
//#supervise
class ActorHierarchyExperiments extends AkkaSpec {
"create top and child actor" in {
//#print-refs
val firstRef = system.actorOf(Props[PrintMyActorRefActor], "first-actor")
println(firstRef)
firstRef ! "printit"
// format: OFF
//#print-refs
val firstRef = system.actorOf(Props[PrintMyActorRefActor], "first-actor")
println(s"First : $firstRef")
firstRef ! "printit"
//#print-refs
// format: ON
}
"start and stop actors" in {
// format: OFF
//#start-stop
val first = system.actorOf(Props[StartStopActor1], "first")
first ! "stop"
val first = system.actorOf(Props[StartStopActor1], "first")
first ! "stop"
//#start-stop
// format: ON
}
"supervise actors" in {
// format: OFF
//#supervise
val supervisingActor = system.actorOf(Props[SupervisingActor], "supervising-actor")
supervisingActor ! "failChild"
//#supervise
}
val supervisingActor = system.actorOf(Props[SupervisingActor], "supervising-actor")
supervisingActor ! "failChild"
//#supervise
// format: ON
}
}

View file

@ -7,7 +7,6 @@ package tutorial_2
import akka.actor.{ Actor, ActorLogging, Props }
object Device {
def props(groupId: String, deviceId: String): Props = Props(new Device(groupId, deviceId))
final case class RecordTemperature(requestId: Long, value: Double)

View file

@ -4,29 +4,28 @@ import tutorial_5.Device.{ ReadTemperature, RecordTemperature, RespondTemperatur
object DeviceInProgress1 {
//#read-protocol-1
object Device {
//#read-protocol-1
final case object ReadTemperature
final case class RespondTemperature(value: Option[Double])
}
//#read-protocol-1
}
}
object DeviceInProgress2 {
//#read-protocol-2
//#device-with-read
import akka.actor.{ Actor, ActorLogging, Props }
object Device {
//#dummy
def props(groupId: String, deviceId: String): Props = Props(new Device(groupId, deviceId))
//#read-protocol-2
final case class ReadTemperature(requestId: Long)
final case class RespondTemperature(requestId: Long, value: Option[Double])
//#dummy
}
//#read-protocol-2
//#device-with-read
import akka.actor.{ Actor, ActorLogging }
}
class Device(groupId: String, deviceId: String) extends Actor with ActorLogging {
var lastTemperatureReading: Option[Double] = None
@ -47,12 +46,9 @@ object DeviceInProgress2 {
object DeviceInProgress3 {
//#write-protocol-1
object Device {
final case class ReadTemperature(requestId: Long)
final case class RespondTemperature(requestId: Long, value: Option[Double])
final case class RecordTemperature(value: Double)
}
//#write-protocol-1
final case class RecordTemperature(value: Double)
//#write-protocol-1
}
}

View file

@ -9,7 +9,6 @@ import tutorial_3.DeviceManager.{ DeviceRegistered, RequestTrackDevice }
//#device-with-register
object Device {
def props(groupId: String, deviceId: String): Props = Props(new Device(groupId, deviceId))
final case class RecordTemperature(requestId: Long, value: Double)
@ -33,7 +32,8 @@ class Device(groupId: String, deviceId: String) extends Actor with ActorLogging
case RequestTrackDevice(groupId, deviceId) =>
log.warning(
"Ignoring TrackDevice request for {}-{}.This actor is responsible for {}-{}.",
groupId, deviceId, this.groupId, this.deviceId)
groupId, deviceId, this.groupId, this.deviceId
)
case RecordTemperature(id, value) =>
log.info("Recorded temperature reading {} with {}", value, id)

View file

@ -36,11 +36,11 @@ class DeviceGroup(groupId: String) extends Actor with ActorLogging {
override def receive: Receive = {
case trackMsg @ RequestTrackDevice(`groupId`, _) =>
deviceIdToActor.get(trackMsg.deviceId) match {
case Some(ref) =>
ref forward trackMsg
case Some(deviceActor) =>
deviceActor forward trackMsg
case None =>
log.info("Creating device actor for {}", trackMsg.deviceId)
val deviceActor = context.actorOf(Device.props(groupId, trackMsg.deviceId), "device-" + trackMsg.deviceId)
val deviceActor = context.actorOf(Device.props(groupId, trackMsg.deviceId), s"device-${trackMsg.deviceId}")
//#device-group-register
context.watch(deviceActor)
actorToDeviceId += deviceActor -> trackMsg.deviceId
@ -52,7 +52,8 @@ class DeviceGroup(groupId: String) extends Actor with ActorLogging {
case RequestTrackDevice(groupId, deviceId) =>
log.warning(
"Ignoring TrackDevice request for {}. This actor is responsible for {}.",
groupId, this.groupId)
groupId, this.groupId
)
//#device-group-register
//#device-group-remove

View file

@ -8,16 +8,14 @@ import akka.actor.{ Actor, ActorLogging, ActorRef, Props, Terminated }
import tutorial_3.DeviceManager.RequestTrackDevice
//#device-manager-full
//#device-manager-msgs
object DeviceManager {
//#device-manager-msgs
def props(): Props = Props(new DeviceManager)
//#device-manager-msgs
final case class RequestTrackDevice(groupId: String, deviceId: String)
case object DeviceRegistered
//#device-manager-msgs
}
//#device-manager-msgs
class DeviceManager extends Actor with ActorLogging {
var groupIdToActor = Map.empty[String, ActorRef]

View file

@ -32,7 +32,8 @@ class Device(groupId: String, deviceId: String) extends Actor with ActorLogging
case RequestTrackDevice(groupId, deviceId) =>
log.warning(
"Ignoring TrackDevice request for {}-{}.This actor is responsible for {}-{}.",
groupId, deviceId, this.groupId, this.deviceId)
groupId, deviceId, this.groupId, this.deviceId
)
case RecordTemperature(id, value) =>
log.info("Recorded temperature reading {} with {}", value, id)

View file

@ -9,16 +9,13 @@ import tutorial_4.DeviceManager.RequestTrackDevice
import scala.concurrent.duration._
//#query-protocol
object DeviceGroup {
//#query-protocol
def props(groupId: String): Props = Props(new DeviceGroup(groupId))
final case class RequestDeviceList(requestId: Long)
final case class ReplyDeviceList(requestId: Long, ids: Set[String])
//#query-protocol
// ... earlier message types not shown
final case class RequestAllTemperatures(requestId: Long)
final case class RespondAllTemperatures(requestId: Long, temperatures: Map[String, TemperatureReading])
@ -27,8 +24,8 @@ object DeviceGroup {
case object TemperatureNotAvailable extends TemperatureReading
case object DeviceNotAvailable extends TemperatureReading
case object DeviceTimedOut extends TemperatureReading
//#query-protocol
}
//#query-protocol
//#query-added
class DeviceGroup(groupId: String) extends Actor with ActorLogging {
@ -58,7 +55,8 @@ class DeviceGroup(groupId: String) extends Actor with ActorLogging {
case RequestTrackDevice(groupId, deviceId) =>
log.warning(
"Ignoring TrackDevice request for {}. This actor is responsible for {}.",
groupId, this.groupId)
groupId, this.groupId
)
case RequestDeviceList(requestId) =>
sender() ! ReplyDeviceList(requestId, deviceIdToActor.keySet)

View file

@ -37,7 +37,6 @@ class DeviceGroupQuery(
context.watch(deviceActor)
deviceActor ! Device.ReadTemperature(0)
}
}
override def postStop(): Unit = {

View file

@ -32,7 +32,8 @@ class Device(groupId: String, deviceId: String) extends Actor with ActorLogging
case RequestTrackDevice(groupId, deviceId) =>
log.warning(
"Ignoring TrackDevice request for {}-{}.This actor is responsible for {}-{}.",
groupId, deviceId, this.groupId, this.deviceId)
groupId, deviceId, this.groupId, this.deviceId
)
case RecordTemperature(id, value) =>
log.info("Recorded temperature reading {} with {}", value, id)

View file

@ -52,7 +52,8 @@ class DeviceGroup(groupId: String) extends Actor with ActorLogging {
case RequestTrackDevice(groupId, deviceId) =>
log.warning(
"Ignoring TrackDevice request for {}. This actor is responsible for {}.",
groupId, this.groupId)
groupId, this.groupId
)
case RequestDeviceList(requestId) =>
sender() ! ReplyDeviceList(requestId, deviceIdToActor.keySet)