Why Akka and the actor model shine for IoT applications

For building the all-interconnected Internet-of-things (IoT) future, one computational model stands out from the rest

iot ts
Thinkstock

As the buzz over the Internet of Things (IoT) ripples across industries, companies from small startups to industry behemoths rush to launch their IoT products. The dramatic advances in Internet infrastructure, cloud computing, connection bandwidth, and mobile devices over the years have all helped make IoT real. Given the abundance of the ever evolving computing technologies, there are many choices of computational models and platforms for the design and implementation of an IoT product.

Dating back to the 1970s, the actor model didn't gain too much attention until recently. The model revolves around a universal primitive called actor for concurrent and distributed computation. It provides an idiomatic alternative to the more conventional concurrency model that relies on synchronization of shared mutable state using locks. In particular, the message-driven style of non-blocking interactions via immutable messages among actors meshes well with contemporary programming approaches on complex distributed platforms.

IoT meets the actor model

One of the main characteristics of a typical IoT system is that it involves a large number of managed devices, each of which consists of changing internal state. In many cases, these devices are primitive hardware running on some simple network protocol due to constraints in cost and/or power. For instance, thermostats managed by EcoFactor's SaaS combine the use of low-cost thermostat hardware and a low-powered WPAN (Wireless Personal Area Network) protocol called ZigBee.

Such "minimalist" requirements align well with the actor model in which breaking down business logic into minimal tasks for individual actors to handle is part of the model's underlying principle. Actors are lightweight by design; hence they can scale without consuming excessive computing resources. Another signature attribute of actors is their loose-coupling by means of non-blocking communications via message passing. These attributes make them suitable for building distributed computing systems.

Yet another important attribute of actors is that each actor can spawn child actors with programmable supervision strategies, suitable for simulating device managers and hierarchical groups of IoT devices. But perhaps the most significant strength of actors is their inherent capability of maintaining their internal state in a virtually private thread isolated from the rest of the system. All of these characteristics make actors superb for simulating IoT devices, each of which consists of its own state and control logic.

Akka actors

Akka is by far the most prominent actor implementation today, providing a robust toolkit on the JVM (Java Virtual Machine). Written in Scala, Akka has all the goodies the standard actor model has to offer. All actors have a well-defined lifecycle with refined hooks such as preStart(), postRestart(), and postStop() for lifecycle logic control. With these lifecycle management hooks and comprehensive actor supervision strategies, one can fully customize an actor's behavior throughout its lifecycle. In the case of simulating an IoT device, one can easily anchor custom initialization and termination routines to the appropriate hooks.

Akka applications might look a little "unconventional" without a message-driven programming background. Nonetheless, from a high-level view, a standard actor code snippet can be quite self-explanatory. For instance, a device actor written in Scala might look something like this:

object Device {
def props(deviceType: String, mqttPubSub: ActorRef) = // ...
}

class Device(deviceType: String, mqttPubSub: ActorRef) extends Actor {
import Device._

private var opState: OpState = InitialState(deviceType)
override def preStart(): Unit = // Initialize device's op-state ...
override def postStop(): Unit = // Reset/Shutdown device ...

def receive = {
case ReportOpState =>
// Assemble report data with opState ...
mqttPubSub ! new Publish(Mqtt.topic_report, reportData)
case UpdateOpState(newState) =>
// Update opState with newState ...
mqttPubSub ! new Publish(Mqtt.topic_update, updateResult)
case PowerOff =>
// Shutdown device ...
}
}

The above snippet shows how one might construct a device actor in Scala/Akka that publishes its operational state information to subscribers using the industry standard MQTT (Message Queue Telemetry Transport) publish-subscribe messaging protocol. The intent here isn't to study how to program in Scala or Akka, but to provide a simple example of an Akka actor's easy-to-understand logic flow.

Even though actors are, in principle, designed for carrying out simplistic tasks, that doesn't mean they can't be used for more heavy-duty work that leverages the functionality-rich toolset provided by Akka. Such flexibility allows one to adjust the actor workload in accordance with business requirements. For instance, an actor could be an individual IoT device, or it could represent a set of devices maintained in some sort of key-value map collection.

Actor state changes

Messages that get sent across actors should always be immutable. That said, because actors operate in a virtually private thread, it's generally safe to maintain an actor's state using properly guarded private variables within the actor class. But if one elects to avoid using any mutable objects, Akka actors are also equipped with the functionality of logic flow that emulates a state machine via hot-swapping.

Akka provides a context.become() method, allowing internal state to be hot-swapped inside the actor's message loop. Below is a sample snippet that illustrates how hot-swapping mimics a state machine without having to use a mutable variable for maintaining the actor state:

object Worker {
def props(workerId, String, clusterClient: ActorRef) = // ...
}

class Worker(workerId, String, clusterClient: ActorRef) extends Actor {
import Worker._

override def preStart(): Unit = // Initialize worker's operational state
override def postStop(): Unit = // Terminate worker ...

def sendToMaster(msg: Any): Unit = {
clusterClient ! SendToAll("/user/master/singleton", msg)
}

def receive = idle

def idle: Receive = {
case WorkIsReady =>
sendToMaster(WorkerIsFree(workerId))

case Work =>
// Process work ...
workProcessor ! work
context.become(working)
}

def working: Receive = {
case WorkProcessed(workResult) =>
sendToMaster(WorkIsDone(workerId, workResult))
context.become(idle)

case Work =>
sendToMaster(WorkerIsBusy(workerId))
}
}

Scalability and fault-tolerance

To address the real-world need of scalability as well as fault-tolerance, Akka provides comprehensive features in actor routing, clustering, sharding, and persistence.

Meanwhile, other parts of the Akka ecosystem address specific needs such as streaming or a REST API in an IoT system. A front-runner in the emerging world of reactive systems, Akka Streams is a comprehensive streaming API built on top of Akka actors. Built on top of Akka streams, Akka HTTP provides a stream-oriented REST/HTTP API stack.

Bottom line: If you're going to build a scalable IoT system that involves device state tracking, you should seriously consider using an actor-based technology stack such as Akka.

Copyright © 2017 IDG Communications, Inc.