The latest buzzword in the web service community is “reactive.” We dig beneath the surface of this word and show how you can use Scala and Akka to build systems that are responsive, resilient, elastic, and message-driven.
2. WHO AM I?
Engineering at Nike+/CDT
Past experience at Cisco, Janrain, Simple, IBM/
Tivoli, etc.
Founder of PNWScala, PDXScala, & various
events/groups
11. A SIMPLE EXAMPLE
case class Greeting(name: String)
class GreetingActor extends Actor with ActorLogging {
def receive = {
case Greeting(name) ⇒
log.info("Hello, {}", name)
}
}
val system = ActorSystem("MyExampleSystem")
val greeter = system.actorOf(Props[GreetingActor],
name = "greeter")
greeter ! Greeting("Thomas")
12. case class
class
log.info(
}
}
val
val
name =
greeter
case class Greeting(name: String)
A SIMPLE EXAMPLE
def receive = {
case Greeting(name) ⇒
log.info("Hello, {}", name)
}
val system = ActorSystem("MyExampleSystem")
val greeter = system.actorOf(Props[GreetingActor],
name = "greeter")
greeter ! Greeting("Thomas")
protocol
behavior
actor system
actor creation
sending a message
case class
class
log.info(
}
}
val
val
name =
greeter
case class Greeting(name: String)
class GreetingActor extends Actor with ActorLogging {
def receive = {
case Greeting(name) ⇒
log.info("Hello, {}", name)
}
}
val system = ActorSystem("MyExampleSystem")
val greeter = system.actorOf(Props[GreetingActor],
name = "greeter")
greeter ! Greeting("Thomas")
13. WHAT AKKA PROVIDES
Actor hierarchy with supervision
Flexible message routing
Configurable scheduling via dispatchers
22. SUPERVISOR EXAMPLE
case object DieNow
case class DomainException(failureMsg: String)
extends Exception(failureMsg)
class Child extends Actor {
import context.dispatcher
scheduler.scheduleOnce(Random.nextInt(5000).milliseconds,
self, DieNow)
def receive = {
case DieNow ⇒ throw new DomainException("R.I.P.")
}
}
23. SUPERVISOR EXAMPLE
class TopLevel extends Actor {
def receive = Actor.emptyBehavior
override def preStart() = {
context.actorOf(Props[Child])
}
override def supervisorStrategy =
OneForOneStrategy(maxNrOfRetries = 2,
withinTimeRange = 10.seconds) {
case DomainException(msg) ⇒ Restart
}
}
25. –Merriam-Webster
“a system of rules that explain the correct conduct and
procedures to be followed in formal situations.”
Protocol
26. PROTOCOLS
Define the interaction between actors
Specify clear boundaries of responsibility
Encode external representation of state at a point in time
Demarcate success and failure clearly
27. AVERY SIMPLE PROTOCOL
trait Response
case class Success(res: Array[Byte]) extends Response
case class Failure(err: String) extends Response
31. EXAMPLE
case object RequestWork
case class Work(id: String, data: String)
class Worker(queue: ActorRef) extends Actor with ActorLogging {
override def preStart = {
queue ! RequestWork
}
def receive = {
case w:Work ⇒
log.info(w.data.toUpperCase)
queue ! RequestWork
}
}
32. EXAMPLE
class Queuer extends Actor {
val queuedWork = Queue.empty[Work]
val requestors = Queue.empty[ActorRef]
def receive = {
case RequestWork if (queuedWork.nonEmpty) ⇒
sender ! queuedWork.dequeue
case RequestWork ⇒
requestors.enqueue(sender)
case w:Work if (requestors.nonEmpty) ⇒
requestors.dequeue ! w
case w:Work ⇒
queuedWork.enqueue(w)
}
}
33. PIPELINES
Commonly found in data processing applications
Distinct phases of data responsibility
May span one or more systems
Often have radically varying scaling needs
34. PIPELINES
Each phase has its own failure domain
Back-pressure is managed through the chain
The simplified view
35. PIPELINES
Each phase has a failure domain
Back-pressure is still managed through the
chain
The more typical view
36. PIPELINES
Often just an extension of more basic patterns
Work-pulling is a common component
Keep an eye on Akka Streams!
39. CIRCUIT-BREAKER
class FailingActor extends Actor {
import context.dispatcher
system.scheduler.scheduleOnce(Random.nextInt(5000).milliseconds, self, Die)
def receive = {
case Die ⇒
throw new Exception("I've fallen and I can't get up!")
case s: String ⇒ sender ! s.toUpperCase
}
}
40. CIRCUIT-BREAKER
val breaker =
new CircuitBreaker(context.system.scheduler,
maxFailures = 5,
callTimeout = 10.seconds,
resetTimeout = 1.minute)
def receive = {
case Tick ⇒
val in = Random.alphanumeric.take(10).mkString
breaker.withCircuitBreaker(toUpper ? in) pipeTo self
case s:String ⇒
log.info("Received: {}", s)
}