“Alexa written with Akka” = Aleka

As a way of demonstrating how to write code with Akka, Scala, and functional programming (FP), I started creating a new project this weekend. I named it Aleka, because it may eventually be like Amazon’s Echo/Alexa, written with Akka (and Scala).

(I suppose a better name might be “Ekko,” after Echo, but I have a niece named Aleka, so unless she objects, this works for me.)

Back to top

Background

If you know me, you know that I got stranded in Canada for a while in the winter of 2010, and began writing an application I named Sarah, which was based on “SARAH” in the tv series Eureka. Sarah is basically a crude/preliminary Mac version of Alexa, and uses Mac speech recognition and text-to-speech capabilities to work like Alexa.

The goal of this series of tutorials is to rebuild a lot of the Sarah functionality using Scala, Akka, and functional programming (FP).

See the third video on the Sarah page (“Sarah - Version 2”) to see how one of the latest versions of Sarah works on a Mac.

Back to top

Assumptions

In this post I assume that you are at least moderately comfortable with Scala and SBT, and have at least have a “Hello, World” knowledge of Akka — preferably at least a little more than that. As a result, I don’t go into great detail when discussing the code that follows.

Back to top

1) Create an SBT project

The first step in the process is to create a Simple Build Tool (SBT) project. A simple way to do this is with my sbtmkdirs shell script, but use whatever tool you are comfortable with. Afterwards you should have a standard SBT directory structure, and a build.sbt file like this:

name := "Aleka"

version := "0.1"

scalaVersion := "2.11.7"

resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"

libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.4.9"

scalacOptions += "-deprecation"

The last line is optional, but all of the others are required.

Back to top

2) Create a Scala app

The next step is to create a Scala app to kick off the application. I created mine as a Scala class named Aleka1.scala, in a directory named com/alvinalexander/aleka (under the src/main/scala directory):

package com.alvinalexander.aleka

import akka.actor.Actor
import akka.actor.ActorSystem
import akka.actor.Props
import akka.event.Logging

object Aleka1 extends App {

    // an actor needs an ActorSystem
    val system = ActorSystem("AlekaSystem")

    // create the actors in our play
    val mouth = system.actorOf(Props[Mouth], name = "mouth")
    val brain = system.actorOf(Props[Brain], name = "brain")

    // send the brain two messages
    brain ! Hello
    brain ! "hello"

    Thread.sleep(500)

    // shut down the system
    system.terminate

}

As shown, the code does the following things:

  • It creates a Scala app with object Aleka1 extends App.
  • It creates an ActorSystem.
  • It creates two ActorRef instances: mouth, and brain. You’ll see how these are used in a few moments.
  • Two messages are sent to the brain. One is a case object named Brain. (There’s no way for you to know yet that this is a case object; stand by.) The second message is the string literal "hello".
  • After a short nap, the actor system is shut down with system.terminate.
Back to top

3) Messages

It’s generally a bad idea to use strings as messages between actors, so I created a few messages as case objects, and put them in a file named Messages.scala, in the same directory as Aleka1.scala:

package com.alvinalexander.aleka

/**
 * this file is a container for all of the messages that can 
 * be sent in the application.
 */

case object Hello
case object SayHello

As you saw in Aleka1.scala, the Hello message is sent to the brain with this line of code:

brain ! Hello
Back to top

4) The brain

Next, create a file named Brain.scala in the same directory. It should have these contents:

package com.alvinalexander.aleka

import akka.actor.Actor
import akka.event.Logging
import akka.actor.Props
import akka.actor.ActorRef
import akka.actor.ActorLogging

class Brain extends Actor with ActorLogging {

    // actors we communicate with
    val mouth = context.actorSelection("../mouth")

    // handle messages sent to us
    def receive = {

        case Hello =>
            log.info("Brain got a Hello message")
            mouth ! SayHello

        case unknown =>
            log.info("Brain got an unknown message: " + unknown)

    }

}

This code creates an Akka Actor named Brain. Over time this Brain will learn how to respond to many different messages, but right now the only thing it can respond to is the Hello message (i.e., the Hello case object). Any message that is sent to Brain that is not a Hello will be handled with the unknown case.

A few other notes:

  • Extending ActorLogging is what gives this class a log reference. This is really convenient for logging/debugging.
  • Brain communicates with another actor, which it looks up with the name "../mouth". You’ll see the Mouth class in just a few moments.
  • Akka actors respond to messages in a receive block, as shown here.
  • When Brain receives a Hello message, it logs that, and then forwards a SayHello message to the mouth actor reference.
Back to top

5) The mouth

The last piece of the puzzle for this application is an actor named Mouth. Rather than speaking to you, Mouth will print its output to the command line.

package com.alvinalexander.aleka

import akka.actor.Actor
import akka.event.Logging

class Mouth extends Actor {

    val log = Logging(context.system, this)

    def receive = {

        case SayHello =>
            log.info("Mouth got a SayHello message")
            println("Hello back at you.")

        case unknown =>
            log.info("Mouth got an unknown message: " + unknown)
            println("Mouth: I don't know what that was.")

    }

}

Notes:

  • Mouth intentionally uses a different way to create a log variable. (I wanted to show both approaches.)
  • When Mouth receives a SayHello message, it logs a message saying that it received the message, then prints a message to STDOUT using println.
  • Mouth also has an unknown case, in the event that it’s sent other messages that it doesn’t currently know how to handle.
Back to top

6) The project directory structure

As a quick review, your project directory structure should now look like this, as shown with the tree command:

$ tree .
.
├── build.sbt
├── lib
├── project
├── src
│   ├── main
│   │   ├── java
│   │   ├── resources
│   │   └── scala
│   │       └── com
│   │           └── alvinalexander
│   │               └── aleka
│   │                   ├── Aleka1.scala
│   │                   ├── Brain.scala
│   │                   ├── Messages.scala
│   │                   └── Mouth.scala
│   └── test
│       ├── java
│       ├── resources
│       └── scala
└── target
Back to top

7) Running the application

As a quick review of what should happen, when you type sbt run, Aleka1 (which extends the App trait) will kick off the application. The important thing it does is send these two messages to its brain actor reference:

brain ! Hello
brain ! "hello"

The rest of the application responds to those two messages. When you run the application, the important output you’ll see should look like this:

$ sbt run
[info] Loading global plugins from /Users/al/.sbt/0.13/plugins
[info] Done updating.
[info] Compiling 4 Scala sources ...
[info] Running com.alvinalexander.aleka.Aleka1
[INFO] [09/04/2016 18:49:15.592] [AlekaSystem-akka.actor.default-dispatcher-3] [akka://AlekaSystem/user/brain] Brain got a Hello message
Hello back at you.
[INFO] [09/04/2016 18:49:15.594] [AlekaSystem-akka.actor.default-dispatcher-6] [akka://AlekaSystem/user/mouth] Mouth got a SayHello message
[INFO] [09/04/2016 18:49:15.594] [AlekaSystem-akka.actor.default-dispatcher-3] [akka://AlekaSystem/user/brain] Brain got an unknown message: hello
[success] Total time: 4 s
Back to top

The source code

You can get the source code for this project on Github at this URL:

Back to top

What’s next

In this first step, the code is hardwired to send two messages to the Brain. In the next version of this project, we’ll add the following functionality:

  • Aleka will accept your input from the command line.
  • I’ll show how to turn off Akka logging, so you only need to see those extra messages when you’re debugging your code.
Back to top