English |  Español |  Français |  Italiano |  Português |  Русский |  Shqip

Developing an Akka edge special code

Chapter 3: Running with Akka

You now have a bit of a taste for Akka, the actor model, and how these concepts and tools can help you address concurrency needs. But a necessary step in building any application is actually building the appplication. In this chapter, we'll show you how to go about this, using the preferred tooling of the Scala community, and show you a few additional bits of infrastructure you'll need to make this all work for you, and to help make your system more maintainable.

sbt

sbt is a standard part of the Scala environment used for building libraries and applications. It's not the only choice available to you, but it's definitely the most commonly used and recommended when working with Akka and Scala. You're welcome, of course, to use what ever build tool you feel the most comfortable with, but at the very least it is worth your time to get familiar enough with sbt that you can build projects that are using it, when you need to.

First, of course, you will need to install sbt. You can find the instructions and downloads available on the SBT website. You will want to make sure you have the sbt command in your path, which you can verify by running the sbt command in an empty directory (create one, if necessary). On the first run, sbt will download a number of its own dependencies and then should leave you at prompt that looks something like this:

[info] Loading global plugins from /Users/tlockney/.sbt/plugins
[info] Set current project to default-e05719 (in build file:/Users/tlockney/tmp/sbt-test/)
&gt

If you get something along these lines, you're now ready to use sbt, otherwise you'll need to double check the installation instructions and try again. You can also find help on SBT's mailing list or in the #sbt IRC channel on Freenode.

The sbt build definition

In our combined experience, build tools are all over the map, so to speak, in terms of functionality and capabilities, but there are a handful of defining characteristics that you can use to differentiate some of them. The most immediately obvious of these, to us, is how they expect you to layout your project's source code. On one end of the spectrum, you have tools like Ant and Rake, both of which go no further than suggesting what the default buildfile's name should be. On the other end, Maven and sbt expect a project with a specific structure (though you can override this, should you choose to do so). Mark Harrah, the original developer of sbt, chose to adopt much of the Maven conventions with regard to this layout -- a decision which we feel was wise. For various reasons, we believe this aspect of the tool was an excellent idea.

The structure you'll get used to seeing in sbt-based projects, generally uses the following guidelines, starting from the root directory of the project. All source code and resources intended for the final build artifacts, go under src, with src/main being the location of your regular code and resources, while src/test will hold the various test cases and dependent resources. These directories will each contain one or more subdirectories with the following structure:

  • Scala code should go under src/main/scala, or src/test/scala in the case of tests and test-related utilities.
  • Similarly, Java code goes in src/main/java or src/test/java.
  • Resources, such as configuration files, go in src/main/resources or src/test/resources.

Your various library dependencies can be handled in a couple of different ways. One option is to simply copy these Jar files into a lib directory at the top-level of your project. But more commonly, you will use managed dependencies, which are declared as part of your build file.

The simplest build file format is handled by a build.sbt file. This file is expected to be at the top-level of your project and will generally contain information such as the project name, project version, the Scala version being used and potentially a set of dependencies to include along with any additional remote repositories that should be queried for these dependencies.

To get us started, here's a very simple build.sbt file that can be used for building a project that uses just the basic Akka actor module:

name := "Simple Akka-based Project" 

version := "0.1"

scalaVersion := "2.10.1"

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

In the build.sbt format, you must separate each setting with one full blank line -- this is required by the parser that reads these files. There is a more advanced format that uses full Scala objects to define your build, and you can find more information about this in the sbt documentation.

You will also see a handful of operators used to assign values here. The only ones we will be covering for now, though, are :=, += and ++=. The first of these, :=, is the basic assignment operator. It is used to assign a specific, fixed value to one of the build settings, as seen above for the name, version, and scalaVersion settings. The += operator is used to append a value to an existing setting -- resolvers and libraryDependencies are the most common cases where you'll see this. Finally, the ++= operator is used to append a collection (in the form of a Seq()) to an existing setting. As with the simple append, this is most often used with the resolvers and libraryDependencies settings. For example, you might see libraryDependencies appended to for each dependency:

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

 libraryDependencies += "com.typesafe.akka" %% "akka-remote" % "2.1.2"

Or, alternately, using the Seq approach:

libraryDependencies ++= Seq(
   "com.typesafe.akka" %% "akka-actor" % "2.1.2",
   "com.typesafe.akka" %% "akka-remote" % "2.1.2"
 )

Notice that you did not need to separate each line in the second form — since you're updating a single setting, there is no need to do so.

Compiling and running the code

Once you have your build set up correctly, you will of course need to compile and run your code. You might even have tests you'd like to run to verify you haven't broken anything!

sbt defines quite a few tasks (and allows you to define more, but that's more advanced than we'll be covering here). The most important of those to get familiar with, for now, are update, compile, test, and run. The update task tells sbt to pull down any dependencies that it needs as defined in the build file. The compile and test tasks should be pretty self explanatory. Executing the run task causes sbt to look for any main methods defined in your code. If it finds more than one, it will prompt you to select which to run.

You should also know that there are two standard ways to execute these tasks: one is to simply pass them as arguments to the sbt command (e.g., sbt compile will cause sbt to compile your code). The other option is to run them from within the sbt console -- as shown above when we showed you how to verify you had the sbt command on your path. If you're running them within the sbt console, you can exit sbt using the exit command (there is a difference between tasks and commands in sbt, but that subject is beyond the scope of this book).

Building your Akka-based project with sbt

Now that you've gotten the basics of using sbt, we can walk you through setting up a project to use Akka. We'll show you what you need to run the code shown in the previous chapter and then you can build on this in later chapters. To start, here's all you need in your build.sbt to get things working:

name := "A Simple Akka Project"
 
version := "0.1"
 
scalaVersion := "2.10.1"
 
libraryDependencies ++= Seq(
  "org.eclipse.jetty" % "jetty-server" % "9.0.0.v20130308",
  "org.eclipse.jetty" % "jetty-webapp" % "9.0.0.v20130308",
  "com.typesafe.akka" %% "akka-actor" % "2.1.2"
)

It's as simple as that. This is essentially the previous definition we showed earlier, but with the addition of the Jetty dependencies. You'll also notice two different styles used for declaring the dependencies. In the first two cases, the first two strings are shown with just a single % character, but the akka-actor dependency has two of them. The first two declarations are equivalent to what you'd see in a Maven POM file, just written a bit differently.

The Akka declaration is using a style that's common for Scala libraries -- the extra % character tells sbt to append the Scala binary version (in this case "2.10") to the end of the dependency name ("akka-actor"), in this form: "akka-actor_2.10". You'll see this repeatedly in sbt build files and it's worth getting used to now. It could have been written explicitly as "akka-actor_2.10". The primary reason to use this style is that, when you have a number of dependencies that, in turn, depend on the version of Scala being used, it's simpler and less repetitive to just change the scalaVersion setting.

That's pretty much all there is to it, until you later decide to branch out and add new dependencies or get even more advance by adding sbt plugins to the mix. For instance, you might want to add one of the plugins available for automatically generating IDE configurations. Later in the book we will need to handle some of the examples with an additional Akka module (akka-remote) for handling remote actors.

Akka and application configuration

Akka uses the Typesafe Config library to handle configuring the actor system and the behavior of its component parts. This library supports a number of different formats for your configuration files. But the one we'll focus on is similar to JSON, but more powerful and simpler, known as HOCON ("Human-Optimized Config Object Notation"). The format is very flexible in what it will accept and it's worth reading the documentation to get a feel for it.

The other significant information you need to understand about this library, is that it is built upon the philosophy that your code should never contain default values. Instead, these default values are expected to be set in a file called reference.conf which usually is distributed as part of the JAR file your code lives in. Similarly, the libraries you depend upon will be similarly configured in their JAR files. At the application level, you then have an application.conf file that allows you to override any of these settings. Additionally, you can use system properties to override any settings. System properties are given the highest priority, allowing for very flexible runtime configuration adjustments.

There are quite a few more features supported by this library and we'll be covering a few more of them as we look at configuring Akka, but the best resource if you're curious is the library's own documentation. This library is becoming more pervasive in the Scala community and is already being used for configuring the Play framework (actually, the library was initially developed to supply a common configuration mechanism for Akka and Play). It's worth familiarizing yourself with it on a deeper level.

Akka uses the standard mechanism described above to load a default configuration, which you can override in your application by adding an application.conf file or by setting system properties. This configuration file will typically go in the src/main/resources directory in order to be loaded correctly at runtime. We could show you the most basic configuration file, but it would be hard to show in this book given that it would be entirely empty. In fact, the file doesn't even need to exist. But to give you a sense of what you will typically see, here's a fairly straightforward configuration that includes a few items I'll be talking more about later:

akka {
  event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
  loglevel = DEBUG
  log-config-on-start = on
  actor {
    debug {
      receive = on
      lifecycle = on
      unhandled = on
    }
  }
}

This configuration sets up a typical environment you might use while developing with Akka in order to effectively root out possible issues. You'll want to familiarize yourself with the full documentation on the configuration settings available, but in general terms this example sets up the logging system and turns on a variety of helpful log messages.

You should take note of the structure of this configuration. As with JSON, it's made up of a sequence of nested components, each of with is a child of the object in which it is contained. Each of these objects has a path determined by this structure and you have the option of specify each setting using the same kind of dot notation that's commonly seen in Java properties files. Here's a few functionally identical iterations on one of the lines from the previous configuration to give you an idea of how this can vary:

akka.actor {
  debug.receive = on
}
akka.actor.debug {
  receive = on
}
akka.actor.debug.receive = on

As we go through some of the additional functionality of Akka, we'll point out configuration settings that can be used (and in some cases are necessary) to affect the behavior of the system. You can also use this configuration to define your own settings specific to your application, but that's outside the scope of this book. You should read both the Config library documentation as well as the documentation specific to the Akka configuration if you choose to do so. There are special, though not onerous considerations you'll need to make in order to avoid breaking the handling of the configuration elsewhere.

Logging in Actors and elsewhere

Logging in Akka is handled asynchronously, as you might expect. It does this using a special event bus. You enable this by defining event handlers that should be subscribed to this bus. The default logging handler simply logs to STDOUT and is enabled using the following configuration:

akka.event-handlers = ["akka.event.Logging$DefaultLogger"]

This simple STDOUT logging is often not the most ideal of approaches, so Akka also includes an SLF4J-based event handler included in the akka-slf4j module. You'll need to add this to your libraryDependencies in sbt. You will also want to add a SLF4J backend such as the recommended Logback:

libraryDependencies ++= Seq(
   "com.typesafe.akka" %% "akka-slf4j" % "2.1.2",
   "ch.qos.logback" % "logback-classic" % "1.0.7"
 )

You will also need to replace the default the event handler using the following configuration.

akka.event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]

Once you have this enabled, you can add a basic Logback configuration in src/main/resources.

<configuration>
   <appender name="FILE" class="ch.qos.logback.core.FileAppender">
     <file>logs/application.log</file>
     <encoder>
       <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
     </encoder>
   </appender>
   <root level="info">
     <appender-ref ref="FILE"/>
   </root>
 </configuration>

Within your actors, you can add logging using either the general Logging facility or by mixing in the ActorLogging trait. This later option requires less setup, so we recommend using it. You can also use the LoggingReceive wrapper (along with a bit of configuration) to have messages received by your actor in your logs. The configuration needed is:

akka.loglevel = DEBUG
 akka.actor.debug.receive = on

And your actor code will look very much like you've already seen, with a few small additions:

import akka.actor.{Actor, ActorLogging}
 
 class MyLoggingActor extends Actor with ActorLogging {
   def receive = LoggingReceive {
     case Message() => //...
     case _ => //...
   }
 }

If you're familiar with any of the common Java-based logging frameworks, the logging API in Akka will feel very familiar. The typical logging levels are supported: error, warning, info, and debug. In addition, it supports up to four placeholder parameters in a format string, using the string {} for each position you want to be replaced. Also, as is typical of error-level logging, you can pass an instance of Throwable as the first parameter for any calls to the error method. All of this is available through the log object when the ActorLogging mixin is used. Here are a handful of examples:

log.info("A very simple log message")

 log.warn("Something potentially dangerous may have happened in {}", "current method")

 log.error(anException, "Error encountered trying to do something dangerous. Error message: {}", 
   anException.getMessage)

 log.debug(
   """
   Dumping out a lot of info is typical for a debug statement, but we're just
   going to print the current unix timestamp and process user : {}
   """,
     System.currentTimeMillis, System.getenv.get("USER"))

Finally, for more general logging, you can use the Logging and LoggingAdaptor objects to get access to the logging system. You will not generally directly refer to the LoggingAdapter class, instead you'll use Logging's factory method to create an instance. You need to have a reference to the current ActorSystem, but otherwise this can be used anywhere in your code. This is actually what ActorLogging is using, it just makes it simpler to use inside of an actor. The only other thing you need is to specify the source you want to be used to identify the log statement in the logs. Normally, this will be either an instance of an actor, a simple String, or a class instance. If you need something else, you can define a LogSource[T] implicit value, but we'll leave that as an exercise for the reader:

import akka.event.Logging
 
 val log = Logging(actorSystem, this)
 log.info("Now we have a logging object instantiated!")

Wrap-up

Now that you know how to build your project with sbt, adjust the configuration as necessary and add logging where you need it, you should be ready to move forward. In the next chapters, we'll dive deeper into the core features of Akka that you'll be using as you build your applications.

There has been error in communication with Booktype server. Not sure right now where is the problem.

You should refresh this page.